xbmc / inputstream.ffmpegdirect

Supports streams opened by FFmpeg's libavformat or Kodi's cURL such as plain TS, HLS and DASH (non-DRM) as well as many others. There is support for Archive/Catchup services where there is a replay window and can timeshift across that span. Also provides timeshift for live streams where rewind/pause and fast-forward would not have been available.
GNU General Public License v2.0
59 stars 42 forks source link

Buffer for streams without timeshift #62

Open MSzturc opened 4 years ago

MSzturc commented 4 years ago

When i play non livestream m3u8 streams every 2-3 minutes i notice a shutter in playback. It seams that the hls segment that should be play is still downloading. If I enable timeshift the problem is gone but im not able to seek. I would like to have a buffer for non timeshift based hls streams.

phunkyfish commented 4 years ago

Do you mean if you disable timeshift you are not able to seek?

MSzturc commented 4 years ago

No i try to describe my problem a bit better.

When i use the following configuration:

#KODIPROP:inputstream=inputstream.ffmpegdirect
#KODIPROP:inputstream.ffmpegdirect.manifest_type=hls
#EXTINF:-1,MyChannel
http://127.0.0.1:3002/mystream.m3u8

The stream starts and im able to seek inside the stream. The problem i get there is that about every 2-3 minutes, i get shutters. It looks like a package segment got dropped and the player jumps to the next segment. To fix this problem i have to enable timeshift. With timeshift enabled i can watch my m3u8 perfectly without any drops, but i lost the ability to seek since ffmpegdirect than handles the m3u8 as livestream and doesnt allow me to seek into the future.

So what i want is an mode where i get the buffering from timeshift for streams that a not livestreams

phunkyfish commented 4 years ago

I understand.

For those streams it might be better to let kodi handle it instead of the addon.

What happens if you replace the prop you have with this one property?

#KODIPROP:inputstream=inputstream.ffmpeg

MSzturc commented 4 years ago

Did you mean inputstream.adaptive? Never heared of inputstream.ffmpeg. With inputstream adaptive I face the same problems as with your inputstream addon. I think because both dont cache hls streams. I've done a bit of research to get a better understanding of the szenario. The hls stream is a 1080p stream, has a bitrate of 8-12mbit and is splitted into 1mb segments. In my opinion the problem occurs when the new segment is provided too late ( by to slow dns lookup or other isp related problems )

phunkyfish commented 4 years ago

inputstream.ffmpeg is kodi’s internal inputstream based on ffmpeg. Please try it.

MSzturc commented 4 years ago

Tried it and got the same error as with the other inputstream plugins

2020-07-08 23:26:25.737 T:327514    INFO <general>: Creating InputStream
2020-07-08 23:26:25.900 T:327514    INFO <general>: Creating Demuxer
2020-07-08 23:26:29.830 T:327514    INFO <general>: Opening stream: 4 source: 256
2020-07-08 23:26:29.831 T:327514    INFO <general>: Creating video codec with codec id: 27
2020-07-08 23:26:29.831 T:327514    INFO <general>: CDVDVideoCodecFFmpeg::Open() Using codec: H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
2020-07-08 23:26:29.831 T:327514    INFO <general>: Creating video thread
2020-07-08 23:26:29.831 T:327594    INFO <general>: running thread: video_thread
2020-07-08 23:26:29.933 T:327514    INFO <general>: Opening stream: 5 source: 256
2020-07-08 23:26:29.933 T:327514    INFO <general>: Finding audio codec for: 86018
2020-07-08 23:26:29.933 T:327514    INFO <general>: CDVDAudioCodecFFmpeg::Open() Successful opened audio decoder aac
2020-07-08 23:26:29.934 T:327514    INFO <general>: Creating audio thread
2020-07-08 23:26:29.934 T:327602    INFO <general>: running thread: CVideoPlayerAudio::Process()
2020-07-08 23:26:29.935 T:327594    INFO <general>: CVideoPlayerVideo - Stillframe left, switching to normal playback
2020-07-08 23:26:29.945 T:327602    INFO <general>: CDVDAudioCodecFFmpeg::Open() Successful opened audio decoder aac
2020-07-08 23:26:29.945 T:327602    INFO <general>: Creating audio stream (codec id: 86018, channels: 2, sample rate: 44100, no pass-through)
2020-07-08 23:26:29.957 T:327287    INFO <general>: CActiveAESink::OpenSink - initialize sink
2020-07-08 23:26:30.141 T:327287    INFO <general>: virtual bool CAESinkDARWINOSX::Initialize(AEAudioFormat &, std::string &): Opening default device Built-in Output
2020-07-08 23:26:30.207 T:327237    INFO <general>: GL: Using CVBREF render method
2020-07-08 23:26:30.212 T:327237    INFO <general>: GL: Selecting YUV 2 RGB shader
2020-07-08 23:26:30.216 T:327237    INFO <general>: Using GL_TEXTURE_RECTANGLE
2020-07-08 23:26:30.219 T:327237    INFO <general>: GL: Using CVBREF render method
2020-07-08 23:26:30.221 T:327237    INFO <general>: GL: Selecting YUV 2 RGB shader
2020-07-08 23:26:30.247 T:327237    INFO <general>: Loading skin file: VideoFullScreen.xml, load type: KEEP_IN_MEMORY
2020-07-08 23:26:30.771 T:327594 WARNING <general>: CRenderManager::WaitForBuffer - timeout waiting for buffer
2020-07-08 23:26:31.195 T:327594    INFO <general>: CDVDVideoCodecFFmpeg::CDropControl: calculated diff time: 41708
2020-07-08 23:26:32.785 T:327237    INFO <general>: Loading skin file: DialogPlayerProcessInfo.xml, load type: KEEP_IN_MEMORY
2020-07-08 23:26:45.606 T:327594    INFO <general>: CVideoPlayerVideo - Stillframe left, switching to normal playback
2020-07-08 23:26:46.223 T:327594 WARNING <general>: CRenderManager::WaitForBuffer - timeout waiting for buffer
2020-07-08 23:26:47.913 T:327594    INFO <general>: CVideoPlayerVideo - Stillframe left, switching to normal playback
2020-07-08 23:26:48.538 T:327594 WARNING <general>: CRenderManager::WaitForBuffer - timeout waiting for buffer
2020-07-08 23:27:11.731 T:327237    INFO <general>: CVideoPlayer::CloseFile()
2020-07-08 23:27:11.733 T:327514    INFO <general>: CVideoPlayer::OnExit()
phunkyfish commented 4 years ago

What quality is the stream? Is it a very high quality stream?

MSzturc commented 4 years ago

1080p with 8-12mbit

phunkyfish commented 4 years ago

That seems a reasonable quality. Any way I can get access to a test stream? I can try a few things but need something to work on.

MSzturc commented 4 years ago

I have two sources where this problem occurs:

MSzturc commented 4 years ago

In Kodi v18 i use script.module.livestreamer (https://kodi.wiki/view/Add-on:Livestreamer) as hls cache

with the following code:

serverPath = os.path.join(xbmc.translatePath(addon.getAddonInfo("path")), "livestreamerXBMCLocalProxy.py")
runs = 0
while not runs > 10:
    try:
        requests.get("http://127.0.0.1:19001/version")
        break
    except Exception:
        logger.info(str(Exception))
        xbmc.executebuiltin("RunScript(" + serverPath + ")")
        runs += 1
        xbmc.sleep(600)
livestreamer_url = "http://127.0.0.1:19001/livestreamer/" + urlsafe_b64encode("hlsvariant://" + data['link'])
import xbmc
import base64
import urlparse
import sys
import socket
from SocketServer import ThreadingMixIn
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from livestreamer import Livestreamer

class MyHandler(BaseHTTPRequestHandler):
    def log_message(self, format, *args):
        pass

    """
    Serves a HEAD request
    """
    def do_HEAD(self):
        self.answer_request(0)

    """
    Serves a GET request.
    """
    def do_GET(self):
        self.answer_request(1)

    def answer_request(self, sendData):
        try:
            request_path = self.path[1:]
            if request_path == "stop":
                sys.exit()
            elif request_path == "version":
                self.send_response(200)
                self.end_headers()
                self.wfile.write("Proxy: Running\r\n")
                self.wfile.write("Version: 0.1\r\n")
            elif request_path[0:13] == "livestreamer/":
                realpath = request_path[13:]
                fURL = base64.urlsafe_b64decode(realpath)
                self.serveFile(fURL, sendData)
            else:
                self.send_response(403)
                self.end_headers()
        finally:
                return

    """
    Sends the requested file and add additional headers.
    """
    def serveFile(self, fURL, sendData):
        session = Livestreamer()
        if '|' in fURL:
                sp = fURL.split('|')
                fURL = sp[0]
                headers = dict(urlparse.parse_qsl(sp[1]))
                session.set_option("http-headers", headers)
                session.set_option("http-ssl-verify", False)
                session.set_option("hls-segment-threads", 2)
        try:
            streams = session.streams(fURL)
            self.send_response(200)
        except:
            self.send_response(403)
        finally:
            self.end_headers()

        if (sendData):
            with streams["best"].open() as stream:
                buf = 'INIT'
                while (len(buf) > 0):
                    buf = stream.read(500 * 1024)
                    self.wfile.write(buf)

class Server(HTTPServer):
    """HTTPServer class with timeout."""

    def get_request(self):
        """Get the request and client address from the socket."""
        self.socket.settimeout(5.0)
        result = None
        while result is None:
            try:
                result = self.socket.accept()
            except socket.timeout:
                pass
        result[0].settimeout(1000)
        return result

class ThreadedHTTPServer(ThreadingMixIn, Server):
    """Handle requests in a separate thread."""

HOST_NAME = '127.0.0.1'
PORT_NUMBER = 19001

if __name__ == '__main__':
    sys.stderr = sys.stdout
    server_class = ThreadedHTTPServer
    httpd = server_class((HOST_NAME, PORT_NUMBER), MyHandler)
    while not xbmc.abortRequested:
        httpd.handle_request()
    httpd.server_close()

My plan was to migrate this to ffmpegdirect since it's a more cleaner solution to have this code inside inputstream addon and outside of video addon

phunkyfish commented 4 years ago

Ok, but then this should be in a new addon called inputstream.livestreamer as the stream is no longer being read by ffmpeg. It could be quite a simple addon that doesn’t need a demuxer. So more like inputstream.rtmp.

MSzturc commented 4 years ago

Before developing a new inputstream plugin, i thought that it might be smarter to ask if you enhance your plugin since almost everything you need for this is already developed in your inputstream, the hls player, the cache for timeshift, the http server, the only think that is missing is seeking support inside timeshift mode

phunkyfish commented 4 years ago

Timeshift supports seeking but only backwards. This is accomplished by buffering the stream and then handling all seek requests via the addons own data structure.

When you play a VOD HLS stream all seeking is handled by ffmpeg, I.e. the video stream is not handled or stored in any way in the addon. It’s not possible to mix the two, I.e. use the data structure for backwards and leave ffmpeg handle forwards.

phunkyfish commented 4 years ago

I’m trying to think of a simple way there could be small delay buffer but I don’t see how that is possible without a delay of X seconds on any seek (where X is the buffer size). And even then I’m not sure how to make that work with a VOD stream.