Skip to content

Commit

Permalink
Merge pull request #30 from ant-media/listenPlayerEvents
Browse files Browse the repository at this point in the history
Listen player events
  • Loading branch information
mustafaboleken authored Oct 28, 2024
2 parents b1b4c94 + e0698e1 commit b133504
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 93 deletions.
206 changes: 113 additions & 93 deletions src/web_player.js
Original file line number Diff line number Diff line change
@@ -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 = "<video id='video-player' class='video-js vjs-default-skin vjs-big-play-centered' controls playsinline></video>";

Expand All @@ -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"];
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -398,6 +403,7 @@ export class WebPlayer {
this.ptzMovement = "relative";
this.restAPIPromise = null;
this.isIPCamera = false;
this.playerEvents = WebPlayer.PLAYER_EVENTS
}

initializeFromUrlParams() {
Expand Down Expand Up @@ -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") {

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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');
Expand Down
70 changes: 70 additions & 0 deletions test/embedded-player.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

})


});
Expand Down

0 comments on commit b133504

Please sign in to comment.