From 5ab44663b3ed0112e19f5ca4aba7a975317313bc Mon Sep 17 00:00:00 2001 From: lastpeony Date: Wed, 9 Oct 2024 02:52:27 +0300 Subject: [PATCH 1/2] listen for videojs player events --- src/web_player.js | 204 +++++++++++++++++++++++++--------------------- 1 file changed, 112 insertions(+), 92 deletions(-) diff --git a/src/web_player.js b/src/web_player.js index f46cfd3..a58ed4b 100644 --- a/src/web_player.js +++ b/src/web_player.js @@ -14,6 +14,8 @@ const PTZ_ZOOM_TEXT_BUTTON_ID = "zoom-text"; export class WebPlayer { + static PLAYER_EVENTS = ['abort','canplay','canplaythrough','durationchange','emptied','ended','error','loadeddata','loadedmetadata','loadstart','pause','play','playing','progress','ratechange','seeked','seeking','stalled','suspend','timeupdate','volumechange','waiting','enterpictureinpicture','leavepictureinpicture','fullscreenchange','resize','audioonlymodechange','audiopostermodechange','controlsdisabled','controlsenabled','debugon','debugoff','disablepictureinpicturechanged','dispose','enterFullWindow','error','exitFullWindow','firstplay','fullscreenerror','languagechange','loadedmetadata','loadstart','playerreset','playerresize','posterchange','ready','textdata','useractive','userinactive','usingcustomcontrols','usingnativecontrols']; + static DEFAULT_PLAY_ORDER = ["webrtc", "hls"]; static DEFAULT_PLAY_TYPE = ["mp4", "webm"]; @@ -234,6 +236,9 @@ export class WebPlayer { WebPlayer.LL_HLS_Folder = "ll-hls"; WebPlayer.VIDEO_PLAYER_ID = "video-player"; + + WebPlayer.PLAYER_EVENTS = ['abort','canplay','canplaythrough','durationchange','emptied','ended','error','loadeddata','loadedmetadata','loadstart','pause','play','playing','progress','ratechange','seeked','seeking','stalled','suspend','timeupdate','volumechange','waiting','enterpictureinpicture','leavepictureinpicture','fullscreenchange','resize','audioonlymodechange','audiopostermodechange','controlsdisabled','controlsenabled','debugon','debugoff','disablepictureinpicturechanged','dispose','enterFullWindow','error','exitFullWindow','firstplay','fullscreenerror','languagechange','loadedmetadata','loadstart','playerreset','playerresize','posterchange','ready','textdata','useractive','userinactive','usingcustomcontrols','usingnativecontrols']; + // Initialize default values this.setDefaults(); @@ -398,6 +403,7 @@ export class WebPlayer { this.ptzMovement = "relative"; this.restAPIPromise = null; this.isIPCamera = false; + this.playerEvents = WebPlayer.PLAYER_EVENTS } initializeFromUrlParams() { @@ -641,22 +647,6 @@ export class WebPlayer { }); - this.videojsPlayer.on('error', (e) => { - Logger.warn("There is an error in playback: ", e); - // We need to add this kind of check. If we don't add this kind of checkpoint, it will create an infinite loop - if (!this.errorCalled) { - this.errorCalled = true; - setTimeout(() => { - this.tryNextTech(); - this.errorCalled = false; - }, 2500) - } - - if (this.playerListener != null) { - this.playerListener("error", e); - } - }); - //webrtc specific events if (extension == "webrtc") { @@ -762,82 +752,7 @@ export class WebPlayer { this.makeVideoJSVisibleWhenReady(); } - this.videojsPlayer.on('ended', () => { - //reinit to play after it ends - Logger.warn("stream is ended") - this.setPlayerVisible(false); - //for webrtc, this event can be called by two reasons - //1. ice connection is not established, it means that there is a networking issug - //2. stream is ended - if (this.currentPlayType != "vod") { - //if it's vod, it means that stream is ended and no need to replay - - if (this.iceConnected) { - //if iceConnected is true, it means that stream is really ended for webrtc - - //initialize to play again if the publishing starts again - this.playIfExists(this.playOrder[0]); - } - else if (this.currentPlayType == "hls") { - //if it's hls, it means that stream is ended - - this.setPlayerVisible(false); - if (this.playOrder[0] = "hls") - { - //do not play again if it's hls because it play last seconds again, let the server clear it - setTimeout(() => { - this.playIfExists(this.playOrder[0]); - }, 10000); - } - else - { - this.playIfExists(this.playOrder[0]); - } - //TODO: what if the stream is hls vod then it always re-play - } - else { - //if iceConnected is false, it means that there is a networking issue for webrtc - this.tryNextTech(); - } - } - if (this.playerListener != null) { - this.playerListener("ended"); - } - }); - - //webrtc plugin sends play event. On the other hand, webrtc plugin sends ready event for every scenario. - //so no need to trust ready event for webrt play - this.videojsPlayer.on("play", () => { - this.setPlayerVisible(true); - if (this.playerListener != null) { - this.playerListener("play"); - } - - if(this.restJwt){ - this.isIpCameraBroadcast(); - } - else if (this.isIPCamera){ - this.injectPtzElements(); - } - }); - - this.videojsPlayer.on("playing", () => { - if (this.playerListener != null) { - this.playerListener("playing"); - } - }); - - this.videojsPlayer.on("timeupdate", () => { - if (this.playerListener != null) { - this.playerListener("timeupdate"); - } - }); - - this.videojsPlayer.on("pause", () => { - if (this.playerListener != null) { - this.playerListener("pause"); - } - }); + this.listenPlayerEvents() this.iceConnected = false; @@ -867,6 +782,111 @@ export class WebPlayer { } } + listenPlayerEvents() { + this.playerEvents.forEach(event => { + this.videojsPlayer.on(event, (eventData) => { + switch (event) { + case 'play': + this.setPlayerVisible(true); + if (this.playerListener != null) { + this.playerListener("play"); + } + + if(this.restJwt){ + this.isIpCameraBroadcast(); + } + else if (this.isIPCamera){ + this.injectPtzElements(); + } + break; + case 'ended': + //reinit to play after it ends + Logger.warn("stream is ended") + this.setPlayerVisible(false); + //for webrtc, this event can be called by two reasons + //1. ice connection is not established, it means that there is a networking issug + //2. stream is ended + if (this.currentPlayType != "vod") { + //if it's vod, it means that stream is ended and no need to replay + + if (this.iceConnected) { + //if iceConnected is true, it means that stream is really ended for webrtc + + //initialize to play again if the publishing starts again + this.playIfExists(this.playOrder[0]); + } + else if (this.currentPlayType == "hls") { + //if it's hls, it means that stream is ended + + this.setPlayerVisible(false); + if (this.playOrder[0] = "hls") + { + //do not play again if it's hls because it play last seconds again, let the server clear it + setTimeout(() => { + this.playIfExists(this.playOrder[0]); + }, 10000); + } + else + { + this.playIfExists(this.playOrder[0]); + } + //TODO: what if the stream is hls vod then it always re-play + } + else { + //if iceConnected is false, it means that there is a networking issue for webrtc + this.tryNextTech(); + } + } + if (this.playerListener != null) { + this.playerListener(event); + } + break; + case 'timeupdate': + if (this.playerListener != null) { + this.playerListener(event, eventData, { currentTime: this.videojsPlayer.currentTime() }); + } + break; + case 'progress': + if (this.playerListener != null) { + this.playerListener(event, eventData, { bufferedPercent: this.videojsPlayer.bufferedPercent() }); + } + break; + case 'volumechange': + if (this.playerListener != null) { + this.playerListener(event, eventData, { + volume: this.videojsPlayer.volume(), + muted: this.videojsPlayer.muted() + }); + } + break; + case 'ratechange': + if (this.playerListener != null) { + this.playerListener(event, eventData, { playbackRate: this.videojsPlayer.playbackRate() }); + } + break; + case 'error': + Logger.warn("There is an error in playback: ", eventData); + // We need to add this kind of check. If we don't add this kind of checkpoint, it will create an infinite loop + if (!this.errorCalled) { + this.errorCalled = true; + setTimeout(() => { + this.tryNextTech(); + this.errorCalled = false; + }, 2500) + } + if(this.playerListener != null){ + this.playerListener("error", eventData) + } + break; + default: + if (this.playerListener != null) { + this.playerListener(event, eventData); + } + } + }); + }); + } + listenForID3MetaData() { this.videojsPlayer.textTracks().on('addtrack', e => { const metadataTrack = Array.from(this.videojsPlayer.textTracks()).find(t => t.label === 'Timed Metadata'); From e0698e1d4ce5adda15a7fa006971d92433c669dc Mon Sep 17 00:00:00 2001 From: lastpeony Date: Wed, 9 Oct 2024 03:49:00 +0300 Subject: [PATCH 2/2] unit test --- src/web_player.js | 2 +- test/embedded-player.test.js | 70 ++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/web_player.js b/src/web_player.js index a58ed4b..fd30731 100644 --- a/src/web_player.js +++ b/src/web_player.js @@ -1,6 +1,6 @@ import { getUrlParameter } from "@antmedia/webrtc_adaptor/dist/fetch.stream"; import { Logger } from "@antmedia/webrtc_adaptor/dist/loglevel.min"; -import { UpArrow } from "./icons/images"; +import { UpArrow } from "./icons/images"; export const STATIC_VIDEO_HTML = ""; diff --git a/test/embedded-player.test.js b/test/embedded-player.test.js index 06d6f27..ee7f44e 100644 --- a/test/embedded-player.test.js +++ b/test/embedded-player.test.js @@ -1032,7 +1032,77 @@ describe("WebPlayer", function() { expect(playWithVideoJS.calledWithMatch(streamPath, WebPlayer.HLS_EXTENSION)).to.be.true; }); + it("Player events", async function() { + this.timeout(10000) + var videoContainer = document.createElement("video_container"); + + var placeHolder = document.createElement("place_holder"); + + var locationComponent = { href : 'http://example.com?id=stream123', search: "?id=stream123", protocol:"http:", pathname: "/", hostname:"example.com", port:5080 }; + var windowComponent = { location : locationComponent, + document: document, + }; + + var player = new WebPlayer(windowComponent, videoContainer, placeHolder); + + var setPlayerVisible = sinon.replace(player, "setPlayerVisible", sinon.fake()); + + // Mock videojsPlayer + player.videojsPlayer = { + on: sinon.stub(), + currentTime: sinon.stub().returns(10), + bufferedPercent: sinon.stub().returns(0.5), + volume: sinon.stub().returns(0.8), + muted: sinon.stub().returns(false), + playbackRate: sinon.stub().returns(1.0), + }; + + player.playerListener = sinon.stub(); + + player.tryNextTech = sinon.stub(); + + player.listenPlayerEvents(); + + player.playerEvents.forEach(event => { + expect(player.videojsPlayer.on.calledWith(event)).to.be.true; + }); + + const playCallback = player.videojsPlayer.on.args.find(arg => arg[0] === 'play')[1]; + playCallback(); + expect(setPlayerVisible.calledWith(true)).to.be.true; + expect(player.playerListener.calledWith("play")).to.be.true; + + const endedCallback = player.videojsPlayer.on.args.find(arg => arg[0] === 'ended')[1]; + endedCallback(); + expect(setPlayerVisible.calledWith(false)).to.be.true; + expect(player.playerListener.calledWith("ended")).to.be.true; + + const pauseCallback = player.videojsPlayer.on.args.find(arg => arg[0] === 'pause')[1]; + pauseCallback(); + expect(player.playerListener.calledWith("pause")).to.be.true; + + const errorCallback = player.videojsPlayer.on.args.find(arg => arg[0] === 'error')[1]; + const errorEventData = { code: 4 }; + errorCallback(errorEventData); + expect(player.playerListener.calledWith("error", errorEventData)).to.be.true; + + const timeUpdateCallback = player.videojsPlayer.on.args.find(arg => arg[0] === 'timeupdate')[1]; + timeUpdateCallback(); + expect(player.playerListener.calledWith('timeupdate', sinon.match.any, { currentTime: 10 })).to.be.true; + + const progressCallback = player.videojsPlayer.on.args.find(arg => arg[0] === 'progress')[1]; + progressCallback(); + expect(player.playerListener.calledWith('progress', sinon.match.any, { bufferedPercent: 0.5 })).to.be.true; + const volumeChangeCallback = player.videojsPlayer.on.args.find(arg => arg[0] === 'volumechange')[1]; + volumeChangeCallback(); + expect(player.playerListener.calledWith('volumechange', sinon.match.any, { volume: 0.8, muted: false })).to.be.true; + + const rateChangeCallback = player.videojsPlayer.on.args.find(arg => arg[0] === 'ratechange')[1]; + rateChangeCallback(); + expect(player.playerListener.calledWith('ratechange', sinon.match.any, { playbackRate: 1.0 })).to.be.true; + + }) });