2020-06-03, 18:17
Can anyone help me?
I'm trying to make Kodi play an MP4 file that was loaded into a Python string, that is, making Kodi stream from memory. My script uses
I managed to make Kodi connect to the socket and I'm able to read the requests, but it doesn't start streaming. It fails to find the MIME type of the file, even though I'm clearly putting it in the headers.
It did work on a rare occasion, so I know that such a thing can be done.
I'm new to server programming and thought this would be simple to do.
The MP4 in question is a small clip from Big Buck Bunny that I downloaded to a file so it could be loaded from the script: https://www.w3schools.com/html/mov_bbb.mp4
Here's my code as "default.py" from the add-on. I can clean it up later, I just wanted to get it working first:
I'm trying to make Kodi play an MP4 file that was loaded into a Python string, that is, making Kodi stream from memory. My script uses
threading.Thread
to create a server socket at localhost
and tries to feed the video file content to Kodi's player. I managed to make Kodi connect to the socket and I'm able to read the requests, but it doesn't start streaming. It fails to find the MIME type of the file, even though I'm clearly putting it in the headers.
It did work on a rare occasion, so I know that such a thing can be done.
I'm new to server programming and thought this would be simple to do.
The MP4 in question is a small clip from Big Buck Bunny that I downloaded to a file so it could be loaded from the script: https://www.w3schools.com/html/mov_bbb.mp4
Here's my code as "default.py" from the add-on. I can clean it up later, I just wanted to get it working first:
python:# -*- coding: UTF-8 -*-
import sys
import socket
from select import select
from threading import Thread
import xbmc
from xbmcaddon import Addon
ADDON = Addon()
# Log a LOGNOTICE-level message.
def xbmcLog(*args):
xbmc.log('MemoryPlayer > ' + ' '.join((var if isinstance(var, str) else repr(var)) for var in args), xbmc.LOGNOTICE)
class MemoryPlayer(xbmc.Player):
def __init__ (self):
xbmc.Player.__init__(self)
def serverPlay(self, data, host, port, listitem=None):
self.keepServing = True
xbmcLog('\n\n\n=======\nserverPlay()\n=======')
self.t = Thread(target=MemoryPlayer.handleServer, args=(self, host, port, data, 'video/mp4'))
self.play('http://%s:%i' % (host, port), listitem=listitem)
self.t.start()
def onPlayBackStarted(self):
xbmcLog('onPlaybackStarted()')
def onPlayBackStopped(self):
self.stopServer()
xbmcLog('onPlaybackStopped()')
def onPlayBackEnded(self):
self.stopServer()
xbmcLog('onPlaybackEnded()')
@staticmethod
def makeHeaders(statusMessage, rangeStart, fileSize, mimeType):
return (
'HTTP/1.0 %s\r\n'
'Accept-Ranges: bytes\r\n'
'Content-Range: bytes %i-%i/%i\r\n'
'Content-Type: %s\r\n'
) % (statusMessage, rangeStart, fileSize-1, fileSize, mimeType)
@staticmethod
def handleServer(self, host, port, data, mimeType):
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.bind((host, port))
serverSocket.listen(1)
SERVER_TUPLE = (serverSocket,)
EMPTY_TUPLE = ( )
fileSize = len(data)
while self.keepServing:
# Blocking call.
readSockets = select(SERVER_TUPLE, EMPTY_TUPLE, EMPTY_TUPLE)[0]
if not readSockets:
xbmcLog('SELECT: empty readSockets')
continue
if not self.keepServing:
break
# Blocking call.
connection, sockname = serverSocket.accept()
xbmcLog('CONNECTION:', sockname)
if not self.keepServing:
break
try:
# recv() can possibly raise an Exception when the other peer disconnects.
input = connection.recv(4096) # 4096 bytes to give enough room for Kodi's standard requests.
xbmcLog('INPUT:', input, '(%i)' % len(input))
# We can take shortcuts since we know we're only dealing with specific Kodi requests.
if input.startswith('GET'):
# Range request. It seems Kodi always sends only the range start (bytes=START-) instead of
# the full range (bytes=START-END).
dataIndex = int(input.split('bytes=', 1)[1].split('-', 1)[0])
xbmcLog('RANGE-START:', dataIndex)
# Assume that the requested range is within bounds.
#dataIndex = min(fileSize, max(0, dataIndex))
output = (
MemoryPlayer.makeHeaders('206 Partial Content', dataIndex, fileSize, mimeType)
+ '\r\n'
+ data[dataIndex:]
)
else:
# HEAD request to probe the MIME type (and maybe length?).
output = MemoryPlayer.makeHeaders('200 OK', 0, fileSize, mimeType)
try:
connection.sendall(output)
except Exception as e:
xbmcLog('SENDALL:', e)
except Exception as e:
xbmcLog('RECV:', e)
self.keepServing = False
# Shutdown the server socket.
try:
serverSocket.shutdown(socket.SHUT_RDWR)
serverSocket.close()
xbmcLog('CLOSE -----')
except Exception as e:
xbmcLog('SHUTDOWN:', e)
self.keepServing = False
videoPath = ADDON.getAddonInfo('path').replace('\\', '/') + '/video.mp4'
with open(videoPath, 'rb') as f:
data = f.read()
# Change 'n' each time you run the add-on to use a different port.
n = 12
PORT = 11100 + n
MemoryPlayer().serverPlay(data, 'localhost', PORT)