phoboslab / jsmpeg

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

Player.Destroy() and then Player.New() with websocket gives error with WebSocket URL #407

Closed GenGol closed 1 year ago

GenGol commented 1 year ago

I'm using JSMpeg using websockets. Player works fine the first time using a webSocket URL. I then I call player.destroy() which works fine.

BUT when I try to call JSMpeg.Player(webSocketURL, options) again I get the following error:

host-report-errors.js?44de:6 Unhandled promise rejection TypeError: Cannot read properties of null (reading 'restoreContext')
    at new WebGLRenderer (jsmpeg.js?bbb2:4180:1)
    at new Player (jsmpeg.js?bbb2:325:1)
    at SourceView.setupNewPlayer (source-view.js?0110:260:1)
    at eval (source-view.js?0110:326:1)
    at eval (es.promise.js?e6cf:107:1)

I've gone into the code and commented out the renderer.destroy() and it works fine:

Player.prototype.destroy = function() {
    this.pause();
    this.source.destroy();
    this.video && this.video.destroy();
    // this.renderer && this.renderer.destroy();
    this.audio && this.audio.destroy();
    this.audioOut && this.audioOut.destroy();
};

This is what my instantiation of the player looks like:

    setupNewPlayer(playerCanvas) {
        this.player = new JSMpeg.Player(this.webSocketUrl, {
            canvas: playerCanvas,
            audio: true,
            source: MetadataWrapper,
            onmeta: data => {
                function pad(value) {
                    return `0${value}`.slice(-2);
                }

                this.leftChan = 0;
                this.rightChan = 0;

                const hour = data[3];
                const minute = data[2];
                const second = data[1];
                const frame = data[0];

                if (frame !== 255) {
                    // Seems that the slicer may not have time data
                    this.slicerTime = `${hour}:${pad(minute)}:${pad(second)};${pad(frame)}`;
                }
                this.leftChan = Math.floor(data[9] * 0.18);
                this.rightChan = Math.floor(data[10] * 0.18);
            },
            protocols: [],
        });
    }

Is there some other way I should be doing this?

phoboslab commented 1 year ago

The above commit should fix this.

Turns out, explicitly calling gl.getExtension('WEBGL_lose_context').loseContext() to destroy the WebGL context is not a good idea. loseContext() should only be used to "simulate" the glcontextlost event. So I have removed this functionality from the WebGL .destroy() method.

Furthermore, the canvas element is only removed from the document when calling .destroy() if it was created by the renderer. If the canvas element was handed over to the renderer (through the options.canvas in the Player constructor), it is left alone.

So, with this you can destroy() and re-create a JSMpeg.Player with the same canvas element, provided that it uses the same rendering method (2d or WebGL) as before.

GenGol commented 1 year ago

I figured there was probably more to it. I've been testing these changes and so far so good. I haven't noticed any issues. Thank you for your help and efforts!!