Hallo,
meine Idee war gar nicht so schlecht, hat auch für Downloads auf Anhieb geklappt, für Streams aber nicht. Warum? Der Pygame.Mixer erkennt im laufenden Stream die MPEG-Frames nicht, versteht nicht, dass es sich um MP3 handelt, und meckert rum, dass er das Format nicht kennt.
Ein MP3-File beginnt immer mit der Zeichenkette "ID3" und chr(3) - das muss man dem Socket beim ersten Zugriff des Pygame-Mixers untermogeln, dann klappt auch das Abspielen des Streams. Hier mein Beispiel (klappt bei mir mit SWR3). Ich habe die mp3streamfile-Klasse dem File Object (echt schlecht beschrieben in der Python-Doku) nachempfunden, die Attribute des Originals braucht man alle nicht und von den Methoden auch nur eine Handvoll.
Was passiert? Ich lege ein mp3streamfile-Objekt an, das erst mal den Zugriff auf den Stream klar macht. Wenn wir das in pygame reinstecken und abspielen, wird mit "read" gelesen. Da liefern wir grundsätzlich den Stream aus, außer bei den ersten beiden Reads, da manipulieren wir das "ID3" rein. Und voila, schon versteht Pygame den Stream.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygame, time, socket
class mp3streamfile():
# in Antwort zu https://forum-raspberrypi.de/forum/thread/8374-mpg123-ueber-python-steuern/?pid=70639#pid70639
import socket
file = None
conn = None
def __init__(self, site, port, path):
self.conn = socket.create_connection((site, port))
http_request = """ HTTP/1.1
Host: {}
"""
self.conn.sendall('GET ' + path + http_request.format(site))
headersstr = self.read_headers(self.conn)
headersdict = self.parse_headers(headersstr)
self.file = self.conn.makefile('rw')
def __iter__(self):
return self
def close(self):
print "MP3Stream: Close called"
self.conn.close()
def next(self):
print "MP3Stream: Next called"
def read(self, size = 0):
print "MP3Stream: Read " + str(size) + " called"
if size == 4:
return "ID3" + chr(3)
if size == 8:
return chr(0) + chr(0) + chr(0) + "JajaJ"
else:
return self.file.read(size)
def readline(self, size = 0):
print "MP3Stream: Readline " + str(size) + " called"
def seek(self, offset, whence = 0):
print "MP3Stream: Seek " + str(offset) + " called"
def tell(self):
print "MP3Stream: Tell called"
return 0
#Die folgenden drei Prozeduren danke ich http://codereview.stackexchange.com/questions/15038/working-with-sockets
def read_line(self, sock):
"read a line from a socket"
chars = []
while True:
a = sock.recv(1)
chars.append(a)
if a == "\n" or a == "":
return "".join(chars)
def read_headers(self, sock):
"read HTTP headers from an internet socket"
lines = []
while True:
line = self.read_line(sock)
if line == "\n" or line == "\r\n" or line == "":
return "".join(lines)
lines.append(line)
def parse_headers(self, headerstr):
"Return a dict, corresponding each HTTP header"
headers = {}
for line in headerstr.splitlines():
k, _, v = line.partition(":")
headers[k] = v.strip()
return headers
print "Streamtest: http://swr-mp3-m-swr3.akacast.akamaistream.net/7/720/137136/v1/gnl.akacast.akamaistream.net/swr-mp3-m-swr3"
pygame.init()
pygame.mixer.init()
site = "swr-mp3-m-swr3.akacast.akamaistream.net"
path = "/7/720/137136/v1/gnl.akacast.akamaistream.net/swr-mp3-m-swr3"
file = mp3streamfile(site, 80, path)
pygame.mixer.music.load(file)
pygame.mixer.music.play()
time.sleep(10)
pygame.mixer.music.stop()
time.sleep(3)
Display More
Ein paar Sachen sind aber doch merkwürdig: pygame verwendet gar nicht Iterator- und Next-Methode. Und wenn man ihm bei "Tell" (was ist meine Position im MP3) eine 0 zurückgibt, dann schluckt er das reibungslos. Das hatte ich ja nun nicht erwartet...
Spannend wird die ganze Geschichte dann noch mal, wenn man das Abspielen in einen Thread verlagert und die Steuerung in einen anderen. Ob der zum File gemachte Socket mitsamt Hack threadsafe ist?
Nächste Aufgabe wird auch sein: Wir sollten aus dem Stream den Titel und Interpreten zur Anzeige herauslesen.
Gruß,
Arne