UNPKG

ovenplayer

Version:

OvenPlayer is Open-Source HTML5 Player. OvenPlayer supports WebRTC Signaling from OvenMediaEngine for Sub-Second Latency Streaming.

571 lines (484 loc) 17.3 kB
/** * Created by hoho on 2018. 6. 27.. */ import Ima from "api/ads/ima/Ad"; import Vast from "api/ads/vast/Ad"; import EventEmitter from "api/EventEmitter"; import EventsListener from "api/provider/html5/Listener"; import { pickCurrentSource } from "api/provider/utils"; import { WARN_MSG_MUTEDPLAY, UI_ICONS, PLAYER_WARNING, STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_COMPLETE, STATE_ERROR, PLAYER_STATE, PLAYER_COMPLETE, PLAYER_PAUSE, PLAYER_PLAY, PLAYER_ZOOM_CHANGED, STATE_AD_PLAYING, STATE_AD_PAUSED, CONTENT_META, CONTENT_TIME, CONTENT_CAPTION_CUE_CHANGED, CONTENT_SOURCE_CHANGED, AD_CLIENT_GOOGLEIMA, AD_CLIENT_VAST, PLAYBACK_RATE_CHANGED, CONTENT_MUTE, PROVIDER_HTML5, PROVIDER_WEBRTC, PROVIDER_DASH, PROVIDER_HLS } from "api/constants"; /** * @brief Core For Html5 Video. * @param spec member value * @param playerConfig player config * @param onExtendedLoad on load handler * */ const Provider = function (spec, playerConfig, onExtendedLoad) { OvenPlayerConsole.log("[Provider] loaded. "); let that = {}; EventEmitter(that); let dashAttachedView = false; let elVideo = spec.element; let ads = null, listener = null, videoEndedCallback = null; let isPlayingProcessing = false; if (spec.adTagUrl) { OvenPlayerConsole.log("[Provider] Ad Client - ", playerConfig.getAdClient()); if (playerConfig.getAdClient() === AD_CLIENT_VAST) { ads = Vast(elVideo, that, playerConfig, spec.adTagUrl); } else { ads = Ima(elVideo, that, playerConfig, spec.adTagUrl); } if (!ads) { console.log("Can not load due to google ima for Ads."); } } listener = EventsListener(elVideo, that, ads ? ads.videoEndedCallback : null, playerConfig); elVideo.playbackRate = elVideo.defaultPlaybackRate = playerConfig.getPlaybackRate(); spec.zoomFactor = 1.0; const _load = (lastPlayPosition) => { const source = spec.sources[spec.currentSource]; spec.framerate = source.framerate; that.setVolume(playerConfig.getVolume()); if (!spec.framerate) { //init timecode mode playerConfig.setTimecodeMode(true); } if (onExtendedLoad) { onExtendedLoad(source, lastPlayPosition); } else { OvenPlayerConsole.log("source loaded : ", source, "lastPlayPosition : " + lastPlayPosition); let previousSource = elVideo.src; // const sourceElement = document.createElement('source'); // sourceElement.src = source.file; const sourceChanged = (source.file !== previousSource); if (sourceChanged) { elVideo.src = source.file; //Don't use this. https://stackoverflow.com/questions/30637784/detect-an-error-on-html5-video //elVideo.append(sourceElement); // Do not call load if src was not set. load() will cancel any active play promise. if (previousSource || previousSource === '') { elVideo.load(); } } that.on(CONTENT_META, function () { if (lastPlayPosition > 0) { that.seek(lastPlayPosition); } }); } }; that.getName = () => { return spec.name; }; that.getMse = () => { return spec.mse; }; that.getMediaElement = () => { return spec.element; }; that.canSeek = () => { return spec.canSeek; }; that.setCanSeek = (canSeek) => { spec.canSeek = canSeek; }; that.isSeeking = () => { return spec.seeking; }; that.setSeeking = (seeking) => { spec.seeking = seeking; }; that.setMetaLoaded = () => { spec.isLoaded = true; }; that.metaLoaded = () => { return spec.isLoaded; }; that.setState = (newState) => { if (spec.state !== newState) { let prevState = spec.state; OvenPlayerConsole.log("Provider : setState()", newState); //ToDo : This is temporary code. If main video occur error, player avoid error message on ad playing. if (prevState === STATE_AD_PLAYING && (newState === STATE_ERROR || newState === STATE_IDLE)) { return false; } /* * 2019-06-13 * No more necessary this codes. * Checking the autoPlay support was using main video element. elVideo.play() -> yes or no?? * And then that causes triggering play and pause event. * And that checking waits for elVideo loaded. Dash load completion time is unknown. * Then I changed check method. I make temporary video tag and insert empty video. * */ //if ((prevState === STATE_AD_PLAYING || prevState === STATE_AD_PAUSED ) && (newState === STATE_PAUSED || newState === STATE_PLAYING)) { // return false; //Ads checks checkAutoplaySupport(). It calls real play() and pause() to video element. //And then that triggers "playing" and "pause". //I prevent these process. //} OvenPlayerConsole.log("Provider : triggerSatatus", newState); switch (newState) { case STATE_COMPLETE: that.trigger(PLAYER_COMPLETE); break; case STATE_PAUSED: that.trigger(PLAYER_PAUSE, { prevState: spec.state, newstate: STATE_PAUSED }); break; case STATE_AD_PAUSED: that.trigger(PLAYER_PAUSE, { prevState: spec.state, newstate: STATE_AD_PAUSED }); break; case STATE_PLAYING: that.trigger(PLAYER_PLAY, { prevState: spec.state, newstate: STATE_PLAYING }); break; case STATE_AD_PLAYING: that.trigger(PLAYER_PLAY, { prevState: spec.state, newstate: STATE_AD_PLAYING }); break; } spec.state = newState; that.trigger(PLAYER_STATE, { prevstate: prevState, newstate: spec.state }); } }; that.getState = () => { return spec.state; }; that.setBuffer = (newBuffer) => { spec.buffer = newBuffer; }; that.getBuffer = () => { return spec.buffer; }; that.isLive = () => { return spec.isLive ? true : (elVideo.duration === Infinity); }; that.getDuration = () => { return that.isLive() ? Infinity : elVideo.duration; }; that.getDvrWindow = () => { return spec.dvrWindow; }; that.getPosition = () => { if (!elVideo) { return 0; } return elVideo.currentTime; }; that.setVolume = (volume) => { if (!elVideo) { return false; } elVideo.volume = volume / 100; playerConfig.setVolume(volume); }; that.getVolume = () => { return playerConfig.getVolume(); }; that.setMute = (state) => { if (!elVideo) { return false; } if (typeof state === 'undefined') { const muted = playerConfig.isMute(); elVideo.muted = !muted; playerConfig.setMute(!muted); that.trigger(CONTENT_MUTE, { mute: playerConfig.isMute() }); } else { elVideo.muted = state; playerConfig.setMute(state); that.trigger(CONTENT_MUTE, { mute: playerConfig.isMute() }); } return elVideo.muted; }; that.getMute = () => { return playerConfig.isMute(); }; that.preload = (sources, lastPlayPosition) => { spec.sources = sources; spec.currentSource = pickCurrentSource(sources, playerConfig); _load(lastPlayPosition || 0); return new Promise(function (resolve, reject) { if (playerConfig.isMute()) { that.setMute(true); } if (playerConfig.getVolume()) { that.setVolume(playerConfig.getVolume()); } resolve(); }); }; that.load = (sources) => { spec.sources = sources; spec.currentSource = pickCurrentSource(sources, playerConfig); _load(0); }; that.play = () => { OvenPlayerConsole.log("Provider : play()"); if (!elVideo) { return false; } //Test it thoroughly and remove isPlayingProcessing. Most of the hazards have been removed. a lot of nonblocking play() way -> blocking play() // if(isPlayingProcessing){ // return false; // } isPlayingProcessing = true; if (that.getState() !== STATE_PLAYING) { if ((ads && ads.isActive()) || (ads && !ads.started())) { ads.play().then(_ => { //ads play success isPlayingProcessing = false; OvenPlayerConsole.log("Provider : ads play success"); }).catch(error => { //ads play fail maybe cause user interactive less isPlayingProcessing = false; OvenPlayerConsole.log("Provider : ads play fail", error); }); } else { let promise = elVideo.play(); if (promise !== undefined) { promise.then(function () { isPlayingProcessing = false; OvenPlayerConsole.log("Provider : video play success"); /* if(mutedPlay){ that.trigger(PLAYER_WARNING, { message : WARN_MSG_MUTEDPLAY, timer : 10 * 1000, iconClass : UI_ICONS.volume_mute, onClickCallback : function(){ that.setMute(false); } }); }*/ }).catch(error => { OvenPlayerConsole.log("Provider : video play error", error.message); isPlayingProcessing = false; // Auto mute and retry play if autoMuteRetryOnNotAllowed config is enabled if (error.name === 'NotAllowedError' && playerConfig.getConfig().autoMuteRetryOnNotAllowed) { that.setMute(true); that.play(true); } /* if(!mutedPlay){ that.setMute(true); that.play(true); } */ }); } else { //IE promise is undefinded. OvenPlayerConsole.log("Provider : video play success (ie)"); isPlayingProcessing = false; } } } }; that.pause = () => { OvenPlayerConsole.log("Provider : pause()"); if (!elVideo) { return false; } if (that.getState() === STATE_PLAYING) { elVideo.pause(); } else if (that.getState() === STATE_AD_PLAYING) { ads.pause(); } }; that.seek = (position) => { if (!elVideo) { return false; } elVideo.currentTime = position; }; that.setPlaybackRate = (playbackRate) => { if (!elVideo) { return false; } that.trigger(PLAYBACK_RATE_CHANGED, { playbackRate: playbackRate }); return elVideo.playbackRate = elVideo.defaultPlaybackRate = playbackRate; }; that.getPlaybackRate = () => { if (!elVideo) { return 0; } return elVideo.playbackRate; }; that.getZoomFactor = () => { return spec.zoomFactor; }; that.setZoomFactor = (zoomFactor) => { that.trigger(PLAYER_ZOOM_CHANGED, { zoomFactor: zoomFactor }); return spec.zoomFactor = zoomFactor; }; that.getSources = () => { if (!elVideo) { return []; } return spec.sources.map(function (source, index) { var obj = { file: source.file, type: source.type, label: source.label, index: index, sectionStart: source.sectionStart, sectionEnd: source.sectionEnd, gridThumbnail: source.gridThumbnail, }; if (source.lowLatency) { obj.lowLatency = source.lowLatency; } return obj; }); }; that.getCurrentSource = () => { return spec.currentSource; }; that.setCurrentSource = (sourceIndex, needProviderChange) => { if (sourceIndex > -1) { if (spec.sources && spec.sources.length > sourceIndex) { //that.pause(); //that.setState(STATE_IDLE); OvenPlayerConsole.log("source changed : " + sourceIndex); spec.currentSource = sourceIndex; playerConfig.setSourceIndex(sourceIndex); //playerConfig.setSourceLabel(spec.sources[sourceIndex].label); //spec.currentQuality = sourceIndex; //that.pause(); that.setState(STATE_IDLE); if (needProviderChange) { _load(elVideo.currentTime || 0); } // return spec.currentSource; } } }; that.getQualityLevels = () => { if (!elVideo) { return []; } return spec.qualityLevels; }; that.getCurrentQuality = () => { if (!elVideo) { return null; } return spec.currentQuality; }; that.setCurrentQuality = (qualityIndex) => { //Do nothing }; that.getAudioTracks = () => { if (!elVideo) { return []; } return spec.audioTracks; }; that.getCurrentAudioTrack = () => { if (!elVideo) { return []; } return spec.currentAudioTrack; }; that.setCurrentAudioTrack = (audioTrackIndex) => { //Do nothing }; that.getSubtitleTracks = () => { if (!elVideo) { return []; } return spec.subtitleTracks; }; that.getCurrentSubtitleTrack = () => { if (!elVideo) { return []; } return spec.currentSubtitleTrack; }; that.setCurrentSubtitleTrack = (subtitleTrackIndex) => { //Do nothing }; that.isAutoQuality = () => { //Do nothing }; that.setAutoQuality = (isAuto) => { //Do nothing }; that.getFramerate = () => { return spec.framerate; }; that.setFramerate = (framerate) => { return spec.framerate = framerate; }; that.seekFrame = (frameCount) => { let fps = spec.framerate; let currentFrames = elVideo.currentTime * fps; let newPosition = (currentFrames + frameCount) / fps; newPosition = newPosition + 0.00001; // FIXES A SAFARI SEEK ISSUE. myVdieo.currentTime = 0.04 would give SMPTE 00:00:00:00 wheras it should give 00:00:00:01 that.pause(); that.seek(newPosition); }; that.stop = () => { if (!elVideo) { return false; } OvenPlayerConsole.log("CORE : stop() "); elVideo.removeAttribute('preload'); elVideo.removeAttribute('src'); while (elVideo.firstChild) { elVideo.removeChild(elVideo.firstChild); } that.pause(); that.setState(STATE_IDLE); isPlayingProcessing = false; }; that.destroy = () => { if (!elVideo) { return false; } that.stop(); listener.destroy(); //elVideo.remove(); if (ads) { ads.destroy(); ads = null; } that.off(); OvenPlayerConsole.log("CORE : destroy() player stop, listener, event destroied"); }; //XXX : I hope using es6 classes. but I think to occur problem from Old IE. Then I choice function inherit. Finally using super function is so difficult. // use : let super_destroy = that.super('destroy'); ... super_destroy(); that.super = (name) => { const method = that[name]; return function () { return method.apply(that, arguments); }; }; return that; }; export default Provider;