phoboslab / jsmpeg

MPEG1 Video Decoder in JavaScript
MIT License
6.36k stars 1.43k forks source link

Support HTTP-TS live-streaming #119

Closed AmpletecGo closed 5 years ago

AmpletecGo commented 7 years ago

Hello master, JSMpeg can support HTTP-TS live-streaming ?

phoboslab commented 7 years ago

What is HTTP-TS?

AmpletecGo commented 7 years ago

https://github.com/ossrs/srs/wiki/v2_EN_DeliveryHttpStream

HTTP TS Live Stream SRS supports remux the rtmp stream to http ts stream, when publish rtmp stream on SRS, SRS will mount a http ts url and when user access the ts url, SRS will remux the rtmp stream to user.

phoboslab commented 7 years ago

So you want to use SRS as the server? I don't think streaming over HTTP is possible in a cross browser way right now.

1) There's no cross-browser way to read binary data of an AJAX call, while the call is still loading 2) The data loaded from the stream would accumulate. We can't throw away old data, so watching the stream will eat more and more RAM

Maybe we could use HTTP Range-Requests here as well, but it seems like a huge hack.

Imho, the better option would be for SRS to support WebSockets :)

AmpletecGo commented 7 years ago

Do you konw https://github.com/Bilibili/flv.js

question 1. We can use nginx http proxy question 2. flv.js do not have this problem.

SRS to support WebSockets it seems like a huge hack.

fetch.txt

phoboslab commented 7 years ago
  1. Maybe. Afaik Nginx can be used as a WebSocket proxy, but you still need a proper WebSocket server behind it. I.e. ffmpeg -> node ws server -> nginx -> client.
  2. Support still seems to be a mess see Livestream playback - compatibility for flv.js. I'm not sure I want to support it at this point.
phoboslab commented 7 years ago

I had a closer look at your implementation for the fetch source and cleaned it up a little. It works great, but only on Chrome - which means I'm not going to put it into the library proper at this point. I'll leave the implementation here as a reference. Thanks!

JSMpeg.Source.Fetch = (function(){ "use strict";

var FetchSource = function(url, options) {
    this.url = url;
    this.destination = null;
    this.request = null;

    this.completed = false;
    this.established = false;
    this.progress = 0;
    this.aborted = false;
};

FetchSource.prototype.connect = function(destination) {
    this.destination = destination;
};

FetchSource.prototype.start = function() {
    var params = {
        method: 'GET',
        headers: new Headers(),
        cache: 'default'
    };

    self.fetch(this.url, params).then(function(res) {
        console.log(res.value);
        if (res.ok && (res.status >= 200 && res.status <= 299)) {
            this.progress = 1;
            this.established = true;
            return this.pump(res.body.getReader());
        }
        else {
            //error
        }
    }.bind(this)).catch(function(err) {
        throw(err);
    });
};

FetchSource.prototype.pump = function(reader) {
    return reader.read().then(function(result) {
        if (result.done) {
            this.completed = true;
        }
        else {
            if (this.aborted) {
                return reader.cancel();
            }

            if (this.destination) {
                this.destination.write(result.value.buffer);
            }

            return this.pump(reader);
        }
    }.bind(this)).catch(function(err) {
        throw(err);
    });
};

FetchSource.prototype.resume = function(secondsHeadroom) {
    // Nothing to do here
};

FetchSource.prototype.abort = function() {
    this.aborted = true;
};

return FetchSource;

})();
AmpletecGo commented 7 years ago

Thanks.

allayli commented 6 years ago

Hi @AmpletecGo @phoboslab , I think supporting HTTP-TS streaming is really awesome. May I have a customer built to support HTTP-TS? Or any branch/forked repo is available?

Thank you very much.

bp2008 commented 5 years ago

Things have changed since February 2017. Most browsers support fetch now. Notably not IE (never IE) and to make it work in Firefox you must toggle some advanced settings.

Last month I developed a new working fetch streaming input module for jsmpeg, based on something I wrote for a different project. Here it is and theoretically it might be more robust than the one posted above. It works great for me anyway. Please feel free to use this however you see fit.

// This source can stream a .ts file using http. No websocket required.
// For browser compatiblity, read: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream
JSMpeg.Source.FetchStream = (function ()
{
    "use strict";

    var FetchSource = function (url, options)
    {
        this.url = url;
        this.options = options;
        this.streamer = null;
        this.includeCredentialsAllOrigins = false;

        this.destination = null;

        this.reconnectInterval = options.reconnectInterval !== undefined
            ? options.reconnectInterval
            : 5;
        this.shouldAttemptReconnect = !!this.reconnectInterval;

        this.completed = false;
        this.established = false;
        this.progress = 0;

        this.reconnectTimeoutId = 0;
    };

    FetchSource.prototype.connect = function (destination)
    {
        this.destination = destination;
    };

    FetchSource.prototype.destroy = function ()
    {
        clearTimeout(this.reconnectTimeoutId);
        this.shouldAttemptReconnect = false;
        if (this.streamer)
            this.streamer.close();
    };

    FetchSource.prototype.start = function ()
    {
        this.shouldAttemptReconnect = !!this.reconnectInterval;
        this.progress = 0;
        this.established = false;

        this.streamer = new FetchStreamer(this);
    };

    FetchSource.prototype.resume = function (secondsHeadroom)
    {
        // Nothing to do here
    };

    FetchSource.prototype.onClose = function ()
    {
        if (this.shouldAttemptReconnect)
        {
            clearTimeout(this.reconnectTimeoutId);
            this.reconnectTimeoutId = setTimeout(function ()
            {
                this.start();
            }.bind(this), this.reconnectInterval * 1000);
        }
    };

    function FetchStreamer(source)
    {
        var self = this;
        var abort_controller = null;
        var reader = null;
        var cancel_streaming = false;

        /**
         * Call when it is time to close the connection.
         */
        this.close = function ()
        {
            cancel_streaming = true;
            if (abort_controller)
            {
                abort_controller.abort();
                abort_controller = null;
            }
            if (reader)
            {
                var cancelPromise = reader.cancel("Streaming canceled");
                if (cancelPromise && cancelPromise["catch"])
                {
                    cancelPromise["catch"](function (err)
                    {
                        if (DOMException && DOMException.ABORT_ERR && err && err.code === DOMException.ABORT_ERR)
                        {
                            // Expected result. Don't spam console.
                        }
                        else if (DOMException && DOMException.INVALID_STATE_ERR && err && err.code === DOMException.INVALID_STATE_ERR)
                        {
                            // Expected result in MS Edge.
                        }
                        else
                            console.error(err);
                    });
                }
                reader = null;
            }
            source.onClose();
        };

        function pump()
        {
            // Do NOT return before the first reader.read() or the fetch can be left in a bad state!
            // Except if reader is null of course.
            if (reader === null)
                return;
            reader.read().then(function (result)
            {
                try
                {
                    if (result.done || cancel_streaming)
                    {
                        self.close();
                        return;
                    }
                    if (source.destination)
                        source.destination.write(result.value);
                    return pump();
                }
                catch (ex)
                {
                    console.error(ex);
                    self.close();
                }
            }
            )["catch"](function (err)
            {
                console.error(err);
                self.close();
            });
        }

        // Prepare to open connection
        var fetchArgs = { credentials: "same-origin" };
        if (source.includeCredentialsAllOrigins)
            fetchArgs.credentials = "include";

        if (typeof AbortController === "function")
        {
            // FF 57+, Edge 16+ (in theory)
            abort_controller = new AbortController();
            fetchArgs.signal = abort_controller.signal;
        }

        // Open connection
        fetch(source.url, fetchArgs)
            .then(function (res)
            {
                try
                {
                    source.progress = 1;
                    source.established = true;

                    if (!res.ok)
                        responseError = res.status + " " + res.statusText;
                    // Do NOT return before the first reader.read() or the fetch can be left in a bad state!
                    reader = res.body.getReader();
                    return pump(reader);
                }
                catch (ex)
                {
                    console.error(ex);
                    self.close();
                }
            })["catch"](function (err)
            {
                console.error(err);
                self.close();
            });
    }

    return FetchSource;
})();
driesken commented 5 years ago

@bp2008 Do you have an update version that works with latest jsmpeg?

bp2008 commented 5 years ago

No, I just have what I posted above.