class Player(protocol.Protocol): # these numbers are also in a dict in Collection. This should obviously be refactored. typeMap = {0: "o", 1: "m", 2: "f", 3: "p"} # ogg # mp3 # flac # pcm (wav etc.) def __init__(self): self.buffer = "" self.display = Display() self.volume = Volume() self.device_type = None self.mac_address = None @property def service(self): return self.factory.service def connectionEstablished(self): """ Called when a connection has been successfully established with the player. """ self.service.evreactor.fireEvent(StateChanged(self, StateChanged.State.ESTABLISHED)) self.render("Connected to Squeal") log.msg("Connected to squeezebox", system="squeal.net.slimproto.Player") def connectionLost(self, reason=protocol.connectionDone): self.service.evreactor.fireEvent(StateChanged(self, StateChanged.State.DISCONNECTED)) self.service.players.remove(self) def dataReceived(self, data): self.buffer = self.buffer + data if len(self.buffer) > 8: operation, length = self.buffer[:4], self.buffer[4:8] length = struct.unpack("!I", length)[0] plen = length + 8 if len(self.buffer) >= plen: packet, self.buffer = self.buffer[8:plen], self.buffer[plen:] operation = operation.strip("!").strip(" ") handler = getattr(self, "process_%s" % operation, None) if handler is None: raise NotImplementedError handler(packet) def send_frame(self, command, data): packet = struct.pack("!H", len(data) + 4) + command + data # print "Sending packet %r" % packet return self.transport.write(packet) def send_version(self): self.send_frame("vers", "7.0") def pack_stream( self, command, autostart="1", formatbyte="o", pcmargs="1321", threshold=255, spdif="0", transDuration=0, transType="0", flags=0x40, outputThreshold=0, replayGainHigh=0, replayGainLow=0, serverPort=9000, serverIp=0, ): return struct.pack( "!ccc4sBcBcBBBHHHL", command, autostart, formatbyte, pcmargs, threshold, spdif, transDuration, transType, flags, outputThreshold, 0, replayGainHigh, replayGainLow, serverPort, serverIp, ) def stop_streaming(self): data = self.pack_stream("q", autostart="0", flags=0) self.send_frame("strm", data) def pause(self): data = self.pack_stream("p", autostart="0", flags=0) self.send_frame("strm", data) log.msg("Sending pause request", system="squeal.net.slimproto.Player") def unpause(self): data = self.pack_stream("u", autostart="0", flags=0) self.send_frame("strm", data) log.msg("Sending unpause request", system="squeal.net.slimproto.Player") def stop(self): self.stop_streaming() def play(self, track): command = "s" autostart = "1" formatbyte = self.typeMap[track.type] data = self.pack_stream(command, autostart=autostart, flags=0x00, formatbyte=formatbyte) request = "GET %s HTTP/1.0\r\n\r\n" % (track.player_uri(id(self)),) data = data + request.encode("utf-8") self.send_frame("strm", data) log.msg("Requesting play from squeezebox %s" % (id(self),), system="squeal.net.slimproto.Player") self.displayTrack(track) def displayTrack(self, track): self.render("%s by %s" % (track.title, track.artist)) def process_HELO(self, data): # (devId, rev, mac, wlan, bytes) = struct.unpack('BB6sHL', data[:16]) (devId, rev, mac) = struct.unpack("BB6s", data[:8]) (mac,) = struct.unpack(">q", "00" + mac) mac = EUI(mac) self.device_type = devices.get(devId, "unknown device") self.mac_address = str(mac) log.msg("HELO received from %s %s" % (self.mac_address, self.device_type), system="squeal.net.slimproto.Player") self.init_client() def init_client(self): self.send_version() self.stop_streaming() self.setBrightness() self.set_visualisation(SpectrumAnalyser()) self.send_frame("setd", struct.pack("B", 0)) self.send_frame("setd", struct.pack("B", 4)) self.enableAudio() self.send_volume() self.send_frame("strm", self.pack_stream("t", autostart="1", flags=0, replayGainHigh=int(time.time() * 1000))) self.connectionEstablished() def enableAudio(self): self.send_frame("aude", struct.pack("2B", 1, 1)) def send_volume(self): og = self.volume.old_gain() ng = self.volume.new_gain() log.msg("Volume set to %d (%d/%d)" % (self.volume.volume, og, ng), system="squeal.net.slimproto.Player") d = self.send_frame("audg", struct.pack("!LLBBLL", og, og, 1, 255, ng, ng)) self.service.evreactor.fireEvent(VolumeChanged(self, self.volume)) return d def setBrightness(self, level=4): assert 0 <= level <= 4 self.send_frame("grfb", struct.pack("!H", level)) def set_visualisation(self, visualisation): self.send_frame("visu", visualisation.pack()) def render(self, text): self.display.clear() self.display.renderText(text, "DejaVu-Sans", 16, (0, 0)) self.updateDisplay(self.display.frame()) def updateDisplay(self, bitmap, transition="c", offset=0, param=0): frame = struct.pack("!Hcb", offset, transition, param) + bitmap self.send_frame("grfe", frame) def process_STAT(self, data): # print "STAT received: %r" % data ev = data[:4] if ev == "\x00\x00\x00\x00": log.msg("Presumed informational stat message", system="squeal.net.slimproto.Player") else: handler = getattr(self, "stat_%s" % ev, None) if handler is None: raise NotImplementedError("Stat message %r not known" % ev) handler(data[4:]) def stat_aude(self, data): log.msg("ACK aude", system="squeal.net.slimproto.Player") def stat_audg(self, data): log.msg("ACK audg", system="squeal.net.slimproto.Player") def stat_strm(self, data): log.msg("ACK strm", system="squeal.net.slimproto.Player") def stat_STMc(self, data): log.msg("Status Message: Connect", system="squeal.net.slimproto.Player") def stat_STMd(self, data): log.msg("Decoder Ready", system="squeal.net.slimproto.Player") self.service.evreactor.fireEvent(StateChanged(self, StateChanged.State.READY)) def stat_STMe(self, data): log.msg("Connection established", system="squeal.net.slimproto.Player") def stat_STMf(self, data): log.msg("Status Message: Connection closed", system="squeal.net.slimproto.Player") def stat_STMh(self, data): log.msg("Status Message: End of headers", system="squeal.net.slimproto.Player") def stat_STMn(self, data): log.msg("Decoder does not support file format", system="squeal.net.slimproto.Player") def stat_STMo(self, data): log.msg("Output Underrun", system="squeal.net.slimproto.Player") def stat_STMp(self, data): log.msg("Pause confirmed", system="squeal.net.slimproto.Player") self.service.evreactor.fireEvent(StateChanged(self, StateChanged.State.PAUSED)) def stat_STMr(self, data): log.msg("Resume confirmed", system="squeal.net.slimproto.Player") self.service.evreactor.fireEvent(StateChanged(self, StateChanged.State.PLAYING)) def stat_STMs(self, data): log.msg("Player status message: playback of new track has started", system="squeal.net.slimproto.Player") self.service.evreactor.fireEvent(StateChanged(self, StateChanged.State.PLAYING)) def stat_STMt(self, data): """ Timer heartbeat """ self.last_heartbeat = time.time() def stat_STMu(self, data): log.msg("End of playback", system="squeal.net.slimproto.Player") self.service.evreactor.fireEvent(StateChanged(self, StateChanged.State.UNDERRUN)) def process_BYE(self, data): log.msg("BYE received", system="squeal.net.slimproto.Player") def process_RESP(self, data): log.msg("RESP received", system="squeal.net.slimproto.Player") def process_BODY(self, data): log.msg("BODY received", system="squeal.net.slimproto.Player") def process_META(self, data): log.msg("META received", system="squeal.net.slimproto.Player") def process_DSCO(self, data): log.msg("Data Stream Disconnected", system="squeal.net.slimproto.Player") def process_DBUG(self, data): log.msg("DBUG received", system="squeal.net.slimproto.Player") def process_IR(self, data): """ Slightly involved codepath here. This raises an event, which may be picked up by the service and then the process_remote_* function in this player will be called. This is mostly relevant for volume changes - most other button presses will require some context to operate. """ (time, code) = struct.unpack("!IxxI", data) command = Remote.codes.get(code, None) if command is not None: log.msg("IR received: %r, %r" % (code, command), system="squeal.net.slimproto.Player") self.service.evreactor.fireEvent(RemoteButtonPressed(self, command)) else: log.msg("Unknown IR received: %r, %r" % (time, code), system="squeal.net.slimproto.Player") def process_RAWI(self, data): log.msg("RAWI received", system="squeal.net.slimproto.Player") def process_ANIC(self, data): log.msg("ANIC received", system="squeal.net.slimproto.Player") def process_BUTN(self, data): log.msg("BUTN received", system="squeal.net.slimproto.Player") def process_KNOB(self, data): log.msg("KNOB received", system="squeal.net.slimproto.Player") def process_SETD(self, data): log.msg("SETD received", system="squeal.net.slimproto.Player") def process_UREQ(self, data): log.msg("UREQ received", system="squeal.net.slimproto.Player") def process_remote_volumeup(self): self.volume.increment() self.send_volume() def process_remote_volumedown(self): self.volume.decrement() self.send_volume()