Skip to content

Instantly share code, notes, and snippets.

@ednisley ednisley/Streamer.py
Created Oct 19, 2016

Embed
What would you like to do?
Python source code: Raspberry Pi streaming radio player -- improved pipe handling
from evdev import InputDevice,ecodes,KeyEvent
import subprocess32 as subp
import select
import re
import sys
import time
import logging
Media = {'KEY_KP7' : ['Classical',False,['mplayer','--quiet','-playlist','http://stream2137.init7.net/listen.pls']],
'KEY_KP8' : ['Jazz',False,['mplayer','--quiet','-playlist','http://stream2138.init7.net/listen.pls']],
'KEY_KP9' : ['WMHT',False,['mplayer','--quiet','http://live.str3am.com:2070/wmht1']],
'KEY_KP4' : ['Classic 1000',True,['mplayer','--quiet','-playlist','http://listen.radionomy.com/1000classicalhits.m3u']],
'KEY_KP5' : ['DCNY 911',False,['mplayer','--quiet','-playlist','http://www.broadcastify.com/scripts/playlists/1/12305/-5857889408.m3u']],
'KEY_KP6' : ['WAMC',False,['mplayer','--quiet','http://pubint.ic.llnwd.net/stream/pubint_wamc']],
'KEY_KP1' : ['60s',True,['mplayer','--quiet','-playlist','http://listen.radionomy.com/all60sallthetime-keepfreemusiccom.m3u']],
'KEY_KP2' : ['50-70s',True,['mplayer','--quiet','-playlist','http://listen.radionomy.com/golden-50-70s-hits.m3u']],
'KEY_KP3' : ['Soft Rock',True,['mplayer','--quiet','-playlist','http://listen.radionomy.com/softrockradio.m3u']],
'KEY_KP0' : ['Zen',True,['mplayer','--quiet','-playlist','http://listen.radionomy.com/zen-for-you.m3u']]
}
CurrentKC = 'KEY_KP3'
Controls = {'KEY_KPSLASH' : '//////',
'KEY_KPASTERISK' : '******',
'KEY_KPENTER' : ' ',
'KEY_KPMINUS' : '<',
'KEY_KPPLUS' : '>',
'KEY_VOLUMEUP' : '*',
'KEY_VOLUMEDOWN' : '/'
}
MuteStrings = ["TargetSpot","[Unknown]","Advert:","+++","---","SRR","Srr","ZEN FOR"]
MuteDelay = 8.0 # delay before non-music track; varies with buffering
UnMuteDelay = 7.5 # delay after non-music track
Muted = False # keep track of muted state
MixerChannel = 'PCM' # which amixer thing to use
logging.basicConfig(format='%(asctime)s %(levelname)s:%(message)s',filename='/tmp/Streamer.log',level=logging.INFO)
logger = logging.getLogger()
# set up event inputs and polling objects
# This requires some udev magic to create the symlinks
k = InputDevice('/dev/input/keypad')
k.grab()
kp = select.poll()
kp.register(k.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
v = InputDevice('/dev/input/volume')
v.grab()
vp = select.poll()
vp.register(v.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
# set up file for output tracing
lw = open('/tmp/mp.log','w') # mplayer piped output
# set the mixer output low enough that the initial stream won't wake the dead
subp.call(['amixer','sset',MixerChannel,'10'])
# Start the player with the default stream, set up for polling
print 'Starting mplayer on',Media[CurrentKC][0],' -> ',Media[CurrentKC][-1][-1]
logging.info('Starting mplayer on %s -> %s',Media[CurrentKC][0],Media[CurrentKC][-1][-1])
p = subp.Popen(Media[CurrentKC][-1],
stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT)
pp = select.poll() # this may be valid for other invocations, but is not pretty
pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
print ' ... running'
#--------------------
#--- Play the streams
while True:
# pluck next line from mplayer and decode it
if [] != pp.poll(10):
text = p.stdout.readline()
if 'ICY Info: ' in text: # these lines may contain track names
lw.write(text)
lw.flush()
trkinfo = text.split(';') # also splits at semicolon embedded in track name
# logging.info('Raw split line: %s', trkinfo)
for ln in trkinfo:
if 'StreamTitle' in ln: # this part contains a track name
NeedMute = False # assume a listenable track
if 2 == ln.count("'"): # exactly two single quotes = probably none embedded in track name
trkhit = re.search(r"StreamTitle='(.*)'",ln)
if trkhit: # true for valid search results
TrackName = trkhit.group(1) # the string between the two quotes
print 'Track name: ', TrackName
logging.info('Track name: [%s]', TrackName)
if Media[CurrentKC][1] and ( (len(TrackName) == 0) or any(m in TrackName for m in MuteStrings) ) :
NeedMute = True
else:
print ' ... semicolon in track name: ', ln
logging.info('Semicolon in track name: [' + ln + ']')
else:
print ' ... quotes in track name: ', ln
logging.info('Quotes in track name: [' + ln + ']')
if NeedMute:
print ' ... muting:',
if Media[CurrentKC][1] and not Muted:
time.sleep(MuteDelay) # brute-force assumption about buffer leadtime
subp.call(['amixer','-q','sset',MixerChannel,'mute'])
Muted = True
print 'done'
logging.info('Track muted')
else:
print ' ... unmuting:',
if Muted:
if Media[CurrentKC][1]:
time.sleep(UnMuteDelay) # another brute-force timing assumption
Muted = False
subp.call(['amixer','-q','sset',MixerChannel,'unmute'])
print 'done'
logging.info('Track unmuted')
elif 'Exiting.' in text: # mplayer just imploded
lw.write(text)
lw.flush()
print 'Got EOF / stream cutoff'
logging.info('EOF or stream cutoff')
print ' ... killing dead mplayer'
pp.unregister(p.stdout.fileno())
p.terminate() # p.kill()
p.wait()
# print ' ... flushing pipes'
# lw.truncate(0)
print ' ... discarding keys'
while [] != kp.poll(0):
kev = k.read
print ' ... restarting mplayer: ',Media[CurrentKC][0]
logging.info('Restarting mplayer')
p = subp.Popen(Media[CurrentKC][-1],
stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT)
pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
print ' ... running'
logging.info(' ... running')
# accept pending events from volume control knob
if [] != vp.poll(10):
vev = v.read()
lw.write('Volume')
lw.flush()
for e in vev:
if e.type == ecodes.EV_KEY:
kc = KeyEvent(e).keycode
# print 'Volume kc: ',kc
if kc in Controls:
print 'Vol Control: ',kc
try:
p.stdin.write(Controls[kc])
except Exception as e:
print "Can't send control: ",e
print ' ... restarting player: ',Media[CurrentKC][0]
logging.info('Error sending volume, restarting player')
pp.unregister(p.stdout.fileno())
p = subp.Popen(Media[CurrentKC][-1],
stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT)
pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
print ' ... running'
logging.info(' ... running')
# accept pending events from keypad
if [] != kp.poll(10):
kev = k.read()
lw.write("Keypad")
lw.flush()
for e in kev:
if e.type == ecodes.EV_KEY:
kc = KeyEvent(e).keycode
if kc == 'KEY_NUMLOCK': # discard these, as we don't care
continue
# print 'Got: ',kc
if (kc == 'KEY_BACKSPACE') and (KeyEvent(e).keystate == KeyEvent.key_hold):
if True:
print 'Backspace = shutdown!'
p.kill()
logging.shutdown()
q = subp.call(['sudo','shutdown','-P','now'])
q.wait()
time.sleep(5)
print "Oddly, we did not die..."
else:
print 'BS = bail from main!'
logging.shutdown()
sys.exit(0)
break
if KeyEvent(e).keystate != KeyEvent.key_down: # discard key up & other rubbish
continue
if kc in Controls:
print 'Control:', kc
try:
p.stdin.write(Controls[kc])
except Exception as e:
print "Can't send control: ",e
print ' ... restarting player: ',Media[CurrentKC][0]
logging.info('Error sending controls, restarting player')
pp.unregister(p.stdout.fileno())
p.terminate() # p.kill()
p.wait()
p = subp.Popen(Media[CurrentKC][-1],
stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT)
pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
print ' ... running'
logging.info(' ... running')
if kc in Media:
print 'Switching stream to ',Media[kc][0],' -> ',Media[kc][-1][-1]
logging.info('Switching stream: ' + Media[kc][0] + ' -> ' + Media[kc][-1][-1])
CurrentKC = kc
print ' ... halting player'
try:
p.communicate(input='q')
except Exception as e:
print 'Perhaps mplayer died?',e
print ' ... killing it for sure'
pp.unregister(p.stdout.fileno())
p.terminate() # p.kill()
p.wait()
# print ' ... flushing pipes'
# lw.truncate(0)
print ' ... restarting player: ',Media[CurrentKC][0]
p = subp.Popen(Media[CurrentKC][-1],
stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT)
pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
print ' ... running'
logging.info(' ... running')
print 'Out of loop!'
logging.shutdown()
@ednisley

This comment has been minimized.

Copy link
Owner Author

commented Oct 19, 2016

More details on my blog at http://wp.me/poZKh-6eT

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.