Reduce triple call to single call, improve video loading, fix embed play button being off-center (#546)
Lots of optimizations and cleanup for the player. If we run into any strange issues, can revert.
This commit is contained in:
parent
0a986b1603
commit
58bdcbd1ed
11 changed files with 302 additions and 190 deletions
|
@ -2,3 +2,5 @@ node_modules/*
|
||||||
./node_modules/**
|
./node_modules/**
|
||||||
**/node_modules/**
|
**/node_modules/**
|
||||||
web/dist/**
|
web/dist/**
|
||||||
|
|
||||||
|
ui/component/viewers/videoViewer/internal/plugins/canAutoplay.js
|
||||||
|
|
|
@ -63,7 +63,7 @@ type Analytics = {
|
||||||
tagFollowEvent: (string, boolean, ?string) => void,
|
tagFollowEvent: (string, boolean, ?string) => void,
|
||||||
playerLoadedEvent: (string, ?boolean) => void,
|
playerLoadedEvent: (string, ?boolean) => void,
|
||||||
playerVideoStartedEvent: (?boolean) => void,
|
playerVideoStartedEvent: (?boolean) => void,
|
||||||
videoStartEvent: (string, number, string, number, string, any, number) => void,
|
videoStartEvent: (?string, number, string, ?number, string, any, ?number) => void,
|
||||||
videoIsPlaying: (boolean, any) => void,
|
videoIsPlaying: (boolean, any) => void,
|
||||||
videoBufferEvent: (
|
videoBufferEvent: (
|
||||||
StreamClaim,
|
StreamClaim,
|
||||||
|
|
|
@ -9,9 +9,9 @@ import * as COLLECTIONS_CONSTS from 'constants/collections';
|
||||||
import {
|
import {
|
||||||
doChangeVolume,
|
doChangeVolume,
|
||||||
doChangeMute,
|
doChangeMute,
|
||||||
doAnalyticsView,
|
|
||||||
doAnalyticsBuffer,
|
doAnalyticsBuffer,
|
||||||
doAnaltyicsPurchaseEvent,
|
doAnaltyicsPurchaseEvent,
|
||||||
|
doAnalyticsView,
|
||||||
} from 'redux/actions/app';
|
} from 'redux/actions/app';
|
||||||
import { selectVolume, selectMute } from 'redux/selectors/app';
|
import { selectVolume, selectMute } from 'redux/selectors/app';
|
||||||
import { savePosition, clearPosition, doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
import { savePosition, clearPosition, doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
||||||
|
@ -76,9 +76,7 @@ const perform = (dispatch) => ({
|
||||||
savePosition: (uri, position) => dispatch(savePosition(uri, position)),
|
savePosition: (uri, position) => dispatch(savePosition(uri, position)),
|
||||||
clearPosition: (uri) => dispatch(clearPosition(uri)),
|
clearPosition: (uri) => dispatch(clearPosition(uri)),
|
||||||
changeMute: (muted) => dispatch(doChangeMute(muted)),
|
changeMute: (muted) => dispatch(doChangeMute(muted)),
|
||||||
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
|
||||||
doAnalyticsBuffer: (uri, bufferData) => dispatch(doAnalyticsBuffer(uri, bufferData)),
|
doAnalyticsBuffer: (uri, bufferData) => dispatch(doAnalyticsBuffer(uri, bufferData)),
|
||||||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
|
||||||
toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()),
|
toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()),
|
||||||
toggleAutoplayNext: () => dispatch(toggleAutoplayNext()),
|
toggleAutoplayNext: () => dispatch(toggleAutoplayNext()),
|
||||||
setVideoPlaybackRate: (rate) => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)),
|
setVideoPlaybackRate: (rate) => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)),
|
||||||
|
@ -95,6 +93,8 @@ const perform = (dispatch) => ({
|
||||||
),
|
),
|
||||||
dispatch(doSetPlayingUri({ uri, collectionId }))
|
dispatch(doSetPlayingUri({ uri, collectionId }))
|
||||||
),
|
),
|
||||||
|
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||||
|
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withRouter(connect(select, perform)(VideoViewer));
|
export default withRouter(connect(select, perform)(VideoViewer));
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.owns=function(a,c){return Object.prototype.hasOwnProperty.call(a,c)};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,c,e){a!=Array.prototype&&a!=Object.prototype&&(a[c]=e.value)};
|
||||||
|
$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global&&null!=global?global:a};$jscomp.global=$jscomp.getGlobal(this);$jscomp.polyfill=function(a,c,e,f){if(c){e=$jscomp.global;a=a.split(".");for(f=0;f<a.length-1;f++){var b=a[f];b in e||(e[b]={});e=e[b]}a=a[a.length-1];f=e[a];c=c(f);c!=f&&null!=c&&$jscomp.defineProperty(e,a,{configurable:!0,writable:!0,value:c})}};
|
||||||
|
$jscomp.polyfill("Object.assign",function(a){return a?a:function(a,e){for(var c=1;c<arguments.length;c++){var b=arguments[c];if(b)for(var g in b)$jscomp.owns(b,g)&&(a[g]=b[g])}return a}},"es6","es3");$jscomp.SYMBOL_PREFIX="jscomp_symbol_";$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.Symbol=function(){var a=0;return function(c){return $jscomp.SYMBOL_PREFIX+(c||"")+a++}}();
|
||||||
|
$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.iterator;a||(a=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&$jscomp.defineProperty(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(a){var c=0;return $jscomp.iteratorPrototype(function(){return c<a.length?{done:!1,value:a[c++]}:{done:!0}})};
|
||||||
|
$jscomp.iteratorPrototype=function(a){$jscomp.initSymbolIterator();a={next:a};a[$jscomp.global.Symbol.iterator]=function(){return this};return a};$jscomp.makeIterator=function(a){$jscomp.initSymbolIterator();var c=a[Symbol.iterator];return c?c.call(a):$jscomp.arrayIterator(a)};$jscomp.FORCE_POLYFILL_PROMISE=!1;
|
||||||
|
$jscomp.polyfill("Promise",function(a){function c(){this.batch_=null}function e(d){return d instanceof b?d:new b(function(a,b){a(d)})}if(a&&!$jscomp.FORCE_POLYFILL_PROMISE)return a;c.prototype.asyncExecute=function(d){null==this.batch_&&(this.batch_=[],this.asyncExecuteBatch_());this.batch_.push(d);return this};c.prototype.asyncExecuteBatch_=function(){var d=this;this.asyncExecuteFunction(function(){d.executeBatch_()})};var f=$jscomp.global.setTimeout;c.prototype.asyncExecuteFunction=function(d){f(d,
|
||||||
|
0)};c.prototype.executeBatch_=function(){for(;this.batch_&&this.batch_.length;){var d=this.batch_;this.batch_=[];for(var a=0;a<d.length;++a){var b=d[a];delete d[a];try{b()}catch(h){this.asyncThrow_(h)}}}this.batch_=null};c.prototype.asyncThrow_=function(d){this.asyncExecuteFunction(function(){throw d;})};var b=function(d){this.state_=0;this.result_=void 0;this.onSettledCallbacks_=[];var a=this.createResolveAndReject_();try{d(a.resolve,a.reject)}catch(l){a.reject(l)}};b.prototype.createResolveAndReject_=
|
||||||
|
function(){function a(a){return function(d){c||(c=!0,a.call(b,d))}}var b=this,c=!1;return{resolve:a(this.resolveTo_),reject:a(this.reject_)}};b.prototype.resolveTo_=function(a){if(a===this)this.reject_(new TypeError("A Promise cannot resolve to itself"));else if(a instanceof b)this.settleSameAsPromise_(a);else{a:switch(typeof a){case "object":var d=null!=a;break a;case "function":d=!0;break a;default:d=!1}d?this.resolveToNonPromiseObj_(a):this.fulfill_(a)}};b.prototype.resolveToNonPromiseObj_=function(a){var b=
|
||||||
|
void 0;try{b=a.then}catch(l){this.reject_(l);return}"function"==typeof b?this.settleSameAsThenable_(b,a):this.fulfill_(a)};b.prototype.reject_=function(a){this.settle_(2,a)};b.prototype.fulfill_=function(a){this.settle_(1,a)};b.prototype.settle_=function(a,b){if(0!=this.state_)throw Error("Cannot settle("+a+", "+b|"): Promise already settled in state"+this.state_);this.state_=a;this.result_=b;this.executeOnSettledCallbacks_()};b.prototype.executeOnSettledCallbacks_=function(){if(null!=this.onSettledCallbacks_){for(var a=
|
||||||
|
this.onSettledCallbacks_,b=0;b<a.length;++b)a[b].call(),a[b]=null;this.onSettledCallbacks_=null}};var g=new c;b.prototype.settleSameAsPromise_=function(a){var b=this.createResolveAndReject_();a.callWhenSettled_(b.resolve,b.reject)};b.prototype.settleSameAsThenable_=function(a,b){var c=this.createResolveAndReject_();try{a.call(b,c.resolve,c.reject)}catch(h){c.reject(h)}};b.prototype.then=function(a,c){function d(a,b){return"function"==typeof a?function(b){try{h(a(b))}catch(m){e(m)}}:b}var h,e,g=new b(function(a,
|
||||||
|
b){h=a;e=b});this.callWhenSettled_(d(a,h),d(c,e));return g};b.prototype.catch=function(a){return this.then(void 0,a)};b.prototype.callWhenSettled_=function(a,b){function c(){switch(d.state_){case 1:a(d.result_);break;case 2:b(d.result_);break;default:throw Error("Unexpected state: "+d.state_);}}var d=this;null==this.onSettledCallbacks_?g.asyncExecute(c):this.onSettledCallbacks_.push(function(){g.asyncExecute(c)})};b.resolve=e;b.reject=function(a){return new b(function(b,c){c(a)})};b.race=function(a){return new b(function(b,
|
||||||
|
c){for(var d=$jscomp.makeIterator(a),g=d.next();!g.done;g=d.next())e(g.value).callWhenSettled_(b,c)})};b.all=function(a){var c=$jscomp.makeIterator(a),d=c.next();return d.done?e([]):new b(function(a,b){function g(b){return function(c){f[b]=c;h--;0==h&&a(f)}}var f=[],h=0;do f.push(void 0),h++,e(d.value).callWhenSettled_(g(f.length-1),b),d=c.next();while(!d.done)})};return b},"es6","es3");
|
||||||
|
(function(a,c){"object"===typeof exports&&"undefined"!==typeof module?module.exports=c():"function"===typeof define&&define.amd?define(c):a.canAutoplay=c()})(this,function(){function a(a){return Object.assign({muted:!1,timeout:250,inline:!1},a)}function c(a,c){var b=a.muted,e=a.timeout;a=a.inline;c=c();var f=c.element;c=c.source;var h=void 0,g=void 0,k=void 0;f.muted=b;!0===b&&f.setAttribute("muted","muted");!0===a&&f.setAttribute("playsinline","playsinline");f.src=c;return new Promise(function(a){h=
|
||||||
|
f.play();g=setTimeout(function(){k(!1,Error("Timeout "+e+" ms has been reached"))},e);k=function(b){var c=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null;clearTimeout(g);a({result:b,error:c})};void 0!==h?h.then(function(){return k(!0)}).catch(function(a){return k(!1,a)}):k(!0)})}var e=new Blob([new Uint8Array([255,227,24,196,0,0,0,3,72,1,64,0,0,4,132,16,31,227,192,225,76,255,67,12,255,221,27,255,228,97,73,63,255,195,131,69,192,232,223,255,255,207,102,239,255,255,255,101,158,206,70,20,
|
||||||
|
59,255,254,95,70,149,66,4,16,128,0,2,2,32,240,138,255,36,106,183,255,227,24,196,59,11,34,62,80,49,135,40,0,253,29,191,209,200,141,71,7,255,252,152,74,15,130,33,185,6,63,255,252,195,70,203,86,53,15,255,255,247,103,76,121,64,32,47,255,34,227,194,209,138,76,65,77,69,51,46,57,55,170,170,170,170,170,170,170,170,170,170,255,227,24,196,73,13,153,210,100,81,135,56,0,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,
|
||||||
|
170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170])],{type:"audio/mpeg"}),f=new Blob([new Uint8Array([0,0,0,28,102,116,121,112,105,115,111,109,0,0,2,0,105,115,111,109,105,115,111,50,109,112,52,49,0,0,0,8,102,114,101,101,0,0,2,239,109,100,97,116,33,16,5,32,164,27,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,167,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,33,16,5,32,164,27,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,55,167,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,0,0,2,194,109,111,111,118,0,0,0,108,109,118,104,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,232,0,0,0,47,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,236,116,114,97,107,0,0,0,92,116,107,104,100,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,101,100,116,115,0,0,0,28,101,108,115,116,0,0,0,0,0,0,0,1,0,0,0,47,0,0,0,0,0,1,0,0,0,0,1,100,109,100,105,97,0,0,0,32,109,100,104,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,172,68,0,0,8,0,85,196,0,0,0,0,0,45,104,100,108,114,0,
|
||||||
|
0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0,0,0,1,15,109,105,110,102,0,0,0,16,115,109,104,100,0,0,0,0,0,0,0,0,0,0,0,36,100,105,110,102,0,0,0,28,100,114,101,102,0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1,0,0,0,211,115,116,98,108,0,0,0,103,115,116,115,100,0,0,0,0,0,0,0,1,0,0,0,87,109,112,52,97,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,16,0,0,0,0,172,68,0,0,0,0,0,51,101,115,100,115,0,0,0,0,3,128,128,128,34,0,2,0,4,128,128,128,20,64,21,0,0,0,0,
|
||||||
|
1,244,0,0,1,243,249,5,128,128,128,2,18,16,6,128,128,128,1,2,0,0,0,24,115,116,116,115,0,0,0,0,0,0,0,1,0,0,0,2,0,0,4,0,0,0,0,28,115,116,115,99,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,2,0,0,0,1,0,0,0,28,115,116,115,122,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,115,0,0,1,116,0,0,0,20,115,116,99,111,0,0,0,0,0,0,0,1,0,0,0,44,0,0,0,98,117,100,116,97,0,0,0,90,109,101,116,97,0,0,0,0,0,0,0,33,104,100,108,114,0,0,0,0,0,0,0,0,109,100,105,114,97,112,112,108,0,0,0,0,0,0,0,0,0,0,0,0,45,105,108,115,116,0,0,0,37,169,116,111,111,0,0,0,
|
||||||
|
29,100,97,116,97,0,0,0,1,0,0,0,0,76,97,118,102,53,54,46,52,48,46,49,48,49])],{type:"video/mp4"});return{audio:function(b){b=a(b);return c(b,function(){return{element:document.createElement("audio"),source:URL.createObjectURL(e)}})},video:function(b){b=a(b);return c(b,function(){return{element:document.createElement("video"),source:URL.createObjectURL(f)}})}}});
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import analytics from 'analytics';
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV !== 'production';
|
const isDev = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
|
@ -25,6 +26,14 @@ const VideoJsEvents = ({
|
||||||
playerRef,
|
playerRef,
|
||||||
autoplaySetting,
|
autoplaySetting,
|
||||||
replay,
|
replay,
|
||||||
|
claimId,
|
||||||
|
userId,
|
||||||
|
claimValues,
|
||||||
|
embedded,
|
||||||
|
uri,
|
||||||
|
doAnalyticsView,
|
||||||
|
claimRewards,
|
||||||
|
playerServerRef,
|
||||||
}: {
|
}: {
|
||||||
tapToUnmuteRef: any, // DOM element
|
tapToUnmuteRef: any, // DOM element
|
||||||
tapToRetryRef: any, // DOM element
|
tapToRetryRef: any, // DOM element
|
||||||
|
@ -33,7 +42,56 @@ const VideoJsEvents = ({
|
||||||
playerRef: any, // DOM element
|
playerRef: any, // DOM element
|
||||||
autoplaySetting: boolean,
|
autoplaySetting: boolean,
|
||||||
replay: boolean,
|
replay: boolean,
|
||||||
|
claimId: ?string,
|
||||||
|
userId: ?number,
|
||||||
|
claimValues: any,
|
||||||
|
embedded: boolean,
|
||||||
|
clearPosition: (string) => void,
|
||||||
|
uri: string,
|
||||||
|
doAnalyticsView: (string, number) => any,
|
||||||
|
claimRewards: () => void,
|
||||||
|
playerServerRef: any
|
||||||
}) => {
|
}) => {
|
||||||
|
/**
|
||||||
|
* Analytics functionality that is run on first video start
|
||||||
|
* @param e - event from videojs (from the plugin?)
|
||||||
|
* @param data - only has secondsToLoad property
|
||||||
|
*/
|
||||||
|
function doTrackingFirstPlay(e: Event, data: any) {
|
||||||
|
// how long until the video starts
|
||||||
|
let timeToStartVideo = data.secondsToLoad;
|
||||||
|
|
||||||
|
analytics.playerVideoStartedEvent(embedded);
|
||||||
|
|
||||||
|
// convert bytes to bits, and then divide by seconds
|
||||||
|
const contentInBits = Number(claimValues.source.size) * 8;
|
||||||
|
const durationInSeconds = claimValues.video && claimValues.video.duration;
|
||||||
|
let bitrateAsBitsPerSecond;
|
||||||
|
if (durationInSeconds) {
|
||||||
|
bitrateAsBitsPerSecond = Math.round(contentInBits / durationInSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out what server the video is served from and then run start analytic event
|
||||||
|
// server string such as 'eu-p6'
|
||||||
|
const playerPoweredBy = playerServerRef.current;
|
||||||
|
|
||||||
|
// populates data for watchman, sends prom and matomo event
|
||||||
|
analytics.videoStartEvent(
|
||||||
|
claimId,
|
||||||
|
timeToStartVideo,
|
||||||
|
playerPoweredBy,
|
||||||
|
userId,
|
||||||
|
uri,
|
||||||
|
this, // pass the player
|
||||||
|
bitrateAsBitsPerSecond
|
||||||
|
);
|
||||||
|
|
||||||
|
// hit backend to mark a view
|
||||||
|
doAnalyticsView(uri, timeToStartVideo).then(() => {
|
||||||
|
claimRewards();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Override the player's control text. We override to:
|
// Override the player's control text. We override to:
|
||||||
// 1. Add keyboard shortcut to the tool-tip.
|
// 1. Add keyboard shortcut to the tool-tip.
|
||||||
// 2. Override videojs' i18n and use our own (don't want to have 2 systems).
|
// 2. Override videojs' i18n and use our own (don't want to have 2 systems).
|
||||||
|
@ -93,6 +151,10 @@ const VideoJsEvents = ({
|
||||||
|
|
||||||
function onInitialPlay() {
|
function onInitialPlay() {
|
||||||
const player = playerRef.current;
|
const player = playerRef.current;
|
||||||
|
|
||||||
|
const bigPlayButton = document.querySelector('.vjs-big-play-button');
|
||||||
|
if (bigPlayButton) bigPlayButton.style.setProperty('display', 'none');
|
||||||
|
|
||||||
if (player && (player.muted() || player.volume() === 0)) {
|
if (player && (player.muted() || player.volume() === 0)) {
|
||||||
// The css starts as "hidden". We make it visible here without
|
// The css starts as "hidden". We make it visible here without
|
||||||
// re-rendering the whole thing.
|
// re-rendering the whole thing.
|
||||||
|
@ -223,6 +285,20 @@ const VideoJsEvents = ({
|
||||||
player.on('volumechange', resolveCtrlText);
|
player.on('volumechange', resolveCtrlText);
|
||||||
player.on('volumechange', onVolumeChange);
|
player.on('volumechange', onVolumeChange);
|
||||||
player.on('error', onError);
|
player.on('error', onError);
|
||||||
|
// custom tracking plugin, event used for watchman data, and marking view/getting rewards
|
||||||
|
player.on('tracking:firstplay', doTrackingFirstPlay);
|
||||||
|
// hide forcing control bar show
|
||||||
|
player.on('canplaythrough', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
// $FlowFixMe
|
||||||
|
const vjsControlBar = document.querySelector('.vjs-control-bar');
|
||||||
|
if (vjsControlBar) vjsControlBar.style.removeProperty('opacity');
|
||||||
|
}, 1000 * 3); // wait 3 seconds to hit control bar
|
||||||
|
});
|
||||||
|
player.on('playing', function() {
|
||||||
|
// $FlowFixMe
|
||||||
|
document.querySelector('.vjs-big-play-button').style.setProperty('display', 'none', 'important');
|
||||||
|
});
|
||||||
// player.on('ended', onEnded);
|
// player.on('ended', onEnded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,48 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
const VideoJsFunctions = ({
|
const VideoJsFunctions = ({
|
||||||
source,
|
|
||||||
sourceType,
|
|
||||||
videoJsOptions,
|
|
||||||
isAudio,
|
isAudio,
|
||||||
}: {
|
}: {
|
||||||
source: string,
|
|
||||||
sourceType: string,
|
|
||||||
videoJsOptions: Object,
|
|
||||||
isAudio: boolean,
|
isAudio: boolean,
|
||||||
}) => {
|
}) => {
|
||||||
function detectFileType() {
|
|
||||||
// $FlowFixMe
|
|
||||||
return new Promise(async (res, rej) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(source, { method: 'HEAD', cache: 'no-store' });
|
|
||||||
|
|
||||||
// Temp variables to hold results
|
|
||||||
let finalType = sourceType;
|
|
||||||
let finalSource = source;
|
|
||||||
|
|
||||||
// override type if we receive an .m3u8 (transcoded mp4)
|
|
||||||
// do we need to check if explicitly redirected
|
|
||||||
// or is checking extension only a safer method
|
|
||||||
if (response && response.redirected && response.url && response.url.endsWith('m3u8')) {
|
|
||||||
finalType = 'application/x-mpegURL';
|
|
||||||
finalSource = response.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify video source in options
|
|
||||||
videoJsOptions.sources = [
|
|
||||||
{
|
|
||||||
src: finalSource,
|
|
||||||
type: finalType,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return res(videoJsOptions);
|
|
||||||
} catch (error) {
|
|
||||||
return rej(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: can remove this function as well
|
// TODO: can remove this function as well
|
||||||
// Create the video DOM element and wrapper
|
// Create the video DOM element and wrapper
|
||||||
function createVideoPlayerDOM(container: any) {
|
function createVideoPlayerDOM(container: any) {
|
||||||
|
@ -61,7 +22,6 @@ const VideoJsFunctions = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
detectFileType,
|
|
||||||
createVideoPlayerDOM,
|
createVideoPlayerDOM,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'videojs-ima'; // loads directly after contrib-ads
|
||||||
import 'video.js/dist/alt/video-js-cdn.min.css';
|
import 'video.js/dist/alt/video-js-cdn.min.css';
|
||||||
import './plugins/videojs-mobile-ui/plugin';
|
import './plugins/videojs-mobile-ui/plugin';
|
||||||
import '@silvermine/videojs-chromecast/dist/silvermine-videojs-chromecast.css';
|
import '@silvermine/videojs-chromecast/dist/silvermine-videojs-chromecast.css';
|
||||||
|
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import * as OVERLAY from './overlays';
|
import * as OVERLAY from './overlays';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
@ -22,6 +21,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||||
import recsys from './plugins/videojs-recsys/plugin';
|
import recsys from './plugins/videojs-recsys/plugin';
|
||||||
// import runAds from './ads';
|
// import runAds from './ads';
|
||||||
import videojs from 'video.js';
|
import videojs from 'video.js';
|
||||||
|
const canAutoplay = require('./plugins/canAutoplay');
|
||||||
|
|
||||||
require('@silvermine/videojs-chromecast')(videojs);
|
require('@silvermine/videojs-chromecast')(videojs);
|
||||||
|
|
||||||
|
@ -73,6 +73,12 @@ type Props = {
|
||||||
playNext: () => void,
|
playNext: () => void,
|
||||||
playPrevious: () => void,
|
playPrevious: () => void,
|
||||||
toggleVideoTheaterMode: () => void,
|
toggleVideoTheaterMode: () => void,
|
||||||
|
claimRewards: () => void,
|
||||||
|
doAnalyticsView: (string, number) => void,
|
||||||
|
uri: string,
|
||||||
|
claimValues: any,
|
||||||
|
clearPosition: (string) => void,
|
||||||
|
centerPlayButton: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
const videoPlaybackRates = [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 1.75, 2];
|
const videoPlaybackRates = [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 1.75, 2];
|
||||||
|
@ -83,18 +89,6 @@ const IS_IOS =
|
||||||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
|
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
|
||||||
!window.MSStream;
|
!window.MSStream;
|
||||||
|
|
||||||
const VIDEO_JS_OPTIONS = {
|
|
||||||
preload: 'auto',
|
|
||||||
playbackRates: videoPlaybackRates,
|
|
||||||
responsive: true,
|
|
||||||
controls: true,
|
|
||||||
html5: {
|
|
||||||
vhs: {
|
|
||||||
overrideNative: !videojs.browser.IS_ANY_SAFARI,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!Object.keys(videojs.getPlugins()).includes('eventTracking')) {
|
if (!Object.keys(videojs.getPlugins()).includes('eventTracking')) {
|
||||||
videojs.registerPlugin('eventTracking', eventTracking);
|
videojs.registerPlugin('eventTracking', eventTracking);
|
||||||
}
|
}
|
||||||
|
@ -142,6 +136,12 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
playNext,
|
playNext,
|
||||||
playPrevious,
|
playPrevious,
|
||||||
toggleVideoTheaterMode,
|
toggleVideoTheaterMode,
|
||||||
|
claimValues,
|
||||||
|
doAnalyticsView,
|
||||||
|
claimRewards,
|
||||||
|
uri,
|
||||||
|
clearPosition,
|
||||||
|
centerPlayButton,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// will later store the videojs player
|
// will later store the videojs player
|
||||||
|
@ -151,16 +151,46 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
const tapToUnmuteRef = useRef();
|
const tapToUnmuteRef = useRef();
|
||||||
const tapToRetryRef = useRef();
|
const tapToRetryRef = useRef();
|
||||||
|
|
||||||
|
const playerServerRef = useRef();
|
||||||
|
|
||||||
// initiate keyboard shortcuts
|
// initiate keyboard shortcuts
|
||||||
const { curried_function } = keyboardShorcuts({ toggleVideoTheaterMode, playNext, playPrevious });
|
const { curried_function } = keyboardShorcuts({ toggleVideoTheaterMode, playNext, playPrevious });
|
||||||
|
|
||||||
const [reload, setReload] = useState('initial');
|
const [reload, setReload] = useState('initial');
|
||||||
|
|
||||||
|
const { createVideoPlayerDOM } = functions({ isAudio });
|
||||||
|
|
||||||
|
const { unmuteAndHideHint, retryVideoAfterFailure, initializeEvents } = events({
|
||||||
|
tapToUnmuteRef,
|
||||||
|
tapToRetryRef,
|
||||||
|
setReload,
|
||||||
|
videoTheaterMode,
|
||||||
|
playerRef,
|
||||||
|
autoplaySetting,
|
||||||
|
replay,
|
||||||
|
claimValues,
|
||||||
|
userId,
|
||||||
|
claimId,
|
||||||
|
embedded,
|
||||||
|
doAnalyticsView,
|
||||||
|
claimRewards,
|
||||||
|
uri,
|
||||||
|
playerServerRef,
|
||||||
|
clearPosition,
|
||||||
|
});
|
||||||
|
|
||||||
const videoJsOptions = {
|
const videoJsOptions = {
|
||||||
...VIDEO_JS_OPTIONS,
|
preload: 'auto',
|
||||||
|
playbackRates: videoPlaybackRates,
|
||||||
|
responsive: true,
|
||||||
|
controls: true,
|
||||||
|
html5: {
|
||||||
|
vhs: {
|
||||||
|
overrideNative: !videojs.browser.IS_ANY_SAFARI,
|
||||||
|
},
|
||||||
|
},
|
||||||
autoplay: autoplay,
|
autoplay: autoplay,
|
||||||
muted: startMuted,
|
muted: startMuted,
|
||||||
sources: [{ src: source, type: sourceType }],
|
|
||||||
poster: poster, // thumb looks bad in app, and if autoplay, flashing poster is annoying
|
poster: poster, // thumb looks bad in app, and if autoplay, flashing poster is annoying
|
||||||
plugins: { eventTracking: true, overlay: OVERLAY.OVERLAY_DATA },
|
plugins: { eventTracking: true, overlay: OVERLAY.OVERLAY_DATA },
|
||||||
// fixes problem of errant CC button showing up on iOS
|
// fixes problem of errant CC button showing up on iOS
|
||||||
|
@ -171,25 +201,14 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
requestTitleFn: (src) => title || '',
|
requestTitleFn: (src) => title || '',
|
||||||
requestSubtitleFn: (src) => channelName || '',
|
requestSubtitleFn: (src) => channelName || '',
|
||||||
},
|
},
|
||||||
|
bigPlayButton: embedded, // only show big play button if embedded
|
||||||
};
|
};
|
||||||
|
|
||||||
const { detectFileType, createVideoPlayerDOM } = functions({ source, sourceType, videoJsOptions, isAudio });
|
|
||||||
|
|
||||||
const { unmuteAndHideHint, retryVideoAfterFailure, initializeEvents } = events({
|
|
||||||
tapToUnmuteRef,
|
|
||||||
tapToRetryRef,
|
|
||||||
setReload,
|
|
||||||
videoTheaterMode,
|
|
||||||
playerRef,
|
|
||||||
autoplaySetting,
|
|
||||||
replay,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize video.js
|
// Initialize video.js
|
||||||
function initializeVideoPlayer(el) {
|
function initializeVideoPlayer(el, canAutoplayVideo) {
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
const vjs = videojs(el, videoJsOptions, () => {
|
const vjs = videojs(el, videoJsOptions, async () => {
|
||||||
const player = playerRef.current;
|
const player = playerRef.current;
|
||||||
const adapter = new playerjs.VideoJSAdapter(player);
|
const adapter = new playerjs.VideoJSAdapter(player);
|
||||||
|
|
||||||
|
@ -209,6 +228,13 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
// Initialize mobile UI.
|
// Initialize mobile UI.
|
||||||
player.mobileUi();
|
player.mobileUi();
|
||||||
|
|
||||||
|
if (!embedded) {
|
||||||
|
window.player.bigPlayButton && window.player.bigPlayButton.hide();
|
||||||
|
} else {
|
||||||
|
const bigPlayButton = document.querySelector('.vjs-big-play-button');
|
||||||
|
if (bigPlayButton) bigPlayButton.style.setProperty('display', 'block', 'important');
|
||||||
|
}
|
||||||
|
|
||||||
Chromecast.initialize(player);
|
Chromecast.initialize(player);
|
||||||
|
|
||||||
// Add quality selector to player
|
// Add quality selector to player
|
||||||
|
@ -228,11 +254,32 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
// set playsinline for mobile
|
// set playsinline for mobile
|
||||||
player.children_[0].setAttribute('playsinline', '');
|
player.children_[0].setAttribute('playsinline', '');
|
||||||
|
|
||||||
|
if (canAutoplayVideo === true) {
|
||||||
|
// show waiting spinner as video is loading
|
||||||
|
player.addClass('vjs-waiting');
|
||||||
|
// document.querySelector('.vjs-big-play-button').style.setProperty('display', 'none', 'important');
|
||||||
|
} else {
|
||||||
|
// $FlowFixMe
|
||||||
|
document.querySelector('.vjs-big-play-button').style.setProperty('display', 'block', 'important');
|
||||||
|
}
|
||||||
|
|
||||||
|
// center play button
|
||||||
|
centerPlayButton();
|
||||||
|
|
||||||
// I think this is a callback function
|
// I think this is a callback function
|
||||||
const videoNode = containerRef.current && containerRef.current.querySelector('video, audio');
|
const videoNode = containerRef.current && containerRef.current.querySelector('video, audio');
|
||||||
|
|
||||||
onPlayerReady(player, videoNode);
|
onPlayerReady(player, videoNode);
|
||||||
adapter.ready();
|
adapter.ready();
|
||||||
|
|
||||||
|
// sometimes video doesnt start properly, this addresses the edge case
|
||||||
|
if (autoplay) {
|
||||||
|
const videoDiv = window.player.children_[0];
|
||||||
|
if (videoDiv) {
|
||||||
|
videoDiv.click();
|
||||||
|
}
|
||||||
|
window.player.userActive(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498)
|
// fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498)
|
||||||
|
@ -245,12 +292,16 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
/** instantiate videoJS and dispose of it when done with code **/
|
/** instantiate videoJS and dispose of it when done with code **/
|
||||||
// This lifecycle hook is only called once (on mount), or when `isAudio` or `source` changes.
|
// This lifecycle hook is only called once (on mount), or when `isAudio` or `source` changes.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const vjsElement = createVideoPlayerDOM(containerRef.current);
|
(async function() {
|
||||||
|
// test if perms to play video are available
|
||||||
|
let canAutoplayVideo = await canAutoplay.video({ timeout: 2000 });
|
||||||
|
|
||||||
|
canAutoplayVideo = canAutoplayVideo.result === true;
|
||||||
|
|
||||||
|
const vjsElement = createVideoPlayerDOM(containerRef.current);
|
||||||
|
|
||||||
// Detect source file type via pre-fetch (async)
|
|
||||||
detectFileType().then(() => {
|
|
||||||
// Initialize Video.js
|
// Initialize Video.js
|
||||||
const vjsPlayer = initializeVideoPlayer(vjsElement);
|
const vjsPlayer = initializeVideoPlayer(vjsElement, canAutoplayVideo);
|
||||||
|
|
||||||
// Add reference to player to global scope
|
// Add reference to player to global scope
|
||||||
window.player = vjsPlayer;
|
window.player = vjsPlayer;
|
||||||
|
@ -259,7 +310,34 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
playerRef.current = vjsPlayer;
|
playerRef.current = vjsPlayer;
|
||||||
|
|
||||||
window.addEventListener('keydown', curried_function(playerRef, containerRef));
|
window.addEventListener('keydown', curried_function(playerRef, containerRef));
|
||||||
});
|
|
||||||
|
// $FlowFixMe
|
||||||
|
document.querySelector('.vjs-control-bar').style.setProperty('opacity', '1', 'important');
|
||||||
|
|
||||||
|
// change to m3u8 if applicable
|
||||||
|
const response = await fetch(source, { method: 'HEAD', cache: 'no-store' });
|
||||||
|
|
||||||
|
playerServerRef.current = response.headers.get('x-powered-by');
|
||||||
|
|
||||||
|
if (response && response.redirected && response.url && response.url.endsWith('m3u8')) {
|
||||||
|
// use m3u8 source
|
||||||
|
// $FlowFixMe
|
||||||
|
vjsPlayer.src({
|
||||||
|
type: 'application/x-mpegURL',
|
||||||
|
src: response.url,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// use original mp4 source
|
||||||
|
// $FlowFixMe
|
||||||
|
vjsPlayer.src({
|
||||||
|
type: sourceType,
|
||||||
|
src: source,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// load video once source setup
|
||||||
|
// $FlowFixMe
|
||||||
|
vjsPlayer.load();
|
||||||
|
})();
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -275,41 +353,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
window.player = undefined;
|
window.player = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [isAudio, source]);
|
}, [isAudio, source, reload]);
|
||||||
|
|
||||||
// Update video player and reload when source URL changes
|
|
||||||
useEffect(() => {
|
|
||||||
// For some reason the video player is responsible for detecting content type this way
|
|
||||||
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
|
|
||||||
let finalType = sourceType;
|
|
||||||
let finalSource = source;
|
|
||||||
|
|
||||||
// override type if we receive an .m3u8 (transcoded mp4)
|
|
||||||
// do we need to check if explicitly redirected
|
|
||||||
// or is checking extension only a safer method
|
|
||||||
if (response && response.redirected && response.url && response.url.endsWith('m3u8')) {
|
|
||||||
finalType = 'application/x-mpegURL';
|
|
||||||
finalSource = response.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify video source in options
|
|
||||||
videoJsOptions.sources = [
|
|
||||||
{
|
|
||||||
src: finalSource,
|
|
||||||
type: finalType,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Update player source
|
|
||||||
const player = playerRef.current;
|
|
||||||
if (!player) return;
|
|
||||||
|
|
||||||
// PR #5570: Temp workaround to avoid double Play button until the next re-architecture.
|
|
||||||
if (!player.paused()) {
|
|
||||||
player.bigPlayButton.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [source, reload]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}>
|
<div className={classnames('video-js-parent', { 'video-js-parent--ios': IS_IOS })} ref={containerRef}>
|
||||||
|
|
|
@ -14,7 +14,6 @@ import AutoplayCountdown from 'component/autoplayCountdown';
|
||||||
import usePrevious from 'effects/use-previous';
|
import usePrevious from 'effects/use-previous';
|
||||||
import FileViewerEmbeddedEnded from 'web/component/fileViewerEmbeddedEnded';
|
import FileViewerEmbeddedEnded from 'web/component/fileViewerEmbeddedEnded';
|
||||||
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
|
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
|
||||||
import LoadingScreen from 'component/common/loading-screen';
|
|
||||||
import { addTheaterModeButton } from './internal/theater-mode';
|
import { addTheaterModeButton } from './internal/theater-mode';
|
||||||
import { addAutoplayNextButton } from './internal/autoplay-next';
|
import { addAutoplayNextButton } from './internal/autoplay-next';
|
||||||
import { addPlayNextButton } from './internal/play-next';
|
import { addPlayNextButton } from './internal/play-next';
|
||||||
|
@ -28,8 +27,8 @@ import type { HomepageCat } from 'util/buildHomepage';
|
||||||
import debounce from 'util/debounce';
|
import debounce from 'util/debounce';
|
||||||
import { formatLbryUrlForWeb, generateListSearchUrlParams } from 'util/url';
|
import { formatLbryUrlForWeb, generateListSearchUrlParams } from 'util/url';
|
||||||
|
|
||||||
const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
|
// const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
|
||||||
const PLAY_TIMEOUT_LIMIT = 2000;
|
// const PLAY_TIMEOUT_LIMIT = 2000;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
position: number,
|
position: number,
|
||||||
|
@ -45,9 +44,7 @@ type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
autoplayNext: boolean,
|
autoplayNext: boolean,
|
||||||
autoplayIfEmbedded: boolean,
|
autoplayIfEmbedded: boolean,
|
||||||
doAnalyticsView: (string, number) => Promise<any>,
|
|
||||||
doAnalyticsBuffer: (string, any) => void,
|
doAnalyticsBuffer: (string, any) => void,
|
||||||
claimRewards: () => void,
|
|
||||||
savePosition: (string, number) => void,
|
savePosition: (string, number) => void,
|
||||||
clearPosition: (string) => void,
|
clearPosition: (string) => void,
|
||||||
toggleVideoTheaterMode: () => void,
|
toggleVideoTheaterMode: () => void,
|
||||||
|
@ -65,6 +62,8 @@ type Props = {
|
||||||
previousListUri: string,
|
previousListUri: string,
|
||||||
videoTheaterMode: boolean,
|
videoTheaterMode: boolean,
|
||||||
isMarkdownOrComment: boolean,
|
isMarkdownOrComment: boolean,
|
||||||
|
doAnalyticsView: (string, number) => void,
|
||||||
|
claimRewards: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -87,8 +86,8 @@ function VideoViewer(props: Props) {
|
||||||
volume,
|
volume,
|
||||||
autoplayNext,
|
autoplayNext,
|
||||||
autoplayIfEmbedded,
|
autoplayIfEmbedded,
|
||||||
doAnalyticsView,
|
|
||||||
doAnalyticsBuffer,
|
doAnalyticsBuffer,
|
||||||
|
doAnalyticsView,
|
||||||
claimRewards,
|
claimRewards,
|
||||||
savePosition,
|
savePosition,
|
||||||
clearPosition,
|
clearPosition,
|
||||||
|
@ -133,7 +132,6 @@ function VideoViewer(props: Props) {
|
||||||
const [adUrl, setAdUrl, isFetchingAd] = useGetAds(approvedVideo, adsEnabled);
|
const [adUrl, setAdUrl, isFetchingAd] = useGetAds(approvedVideo, adsEnabled);
|
||||||
/* isLoading was designed to show loading screen on first play press, rather than completely black screen, but
|
/* isLoading was designed to show loading screen on first play press, rather than completely black screen, but
|
||||||
breaks because some browsers (e.g. Firefox) block autoplay but leave the player.play Promise pending */
|
breaks because some browsers (e.g. Firefox) block autoplay but leave the player.play Promise pending */
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [replay, setReplay] = useState(false);
|
const [replay, setReplay] = useState(false);
|
||||||
const [videoNode, setVideoNode] = useState();
|
const [videoNode, setVideoNode] = useState();
|
||||||
|
|
||||||
|
@ -150,7 +148,6 @@ function VideoViewer(props: Props) {
|
||||||
if (uri && previousUri && uri !== previousUri) {
|
if (uri && previousUri && uri !== previousUri) {
|
||||||
setShowAutoplayCountdown(false);
|
setShowAutoplayCountdown(false);
|
||||||
setIsEndedEmbed(false);
|
setIsEndedEmbed(false);
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
}, [uri, previousUri]);
|
}, [uri, previousUri]);
|
||||||
|
|
||||||
|
@ -170,47 +167,6 @@ function VideoViewer(props: Props) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Analytics functionality that is run on first video start
|
|
||||||
* @param e - event from videojs (from the plugin?)
|
|
||||||
* @param data - only has secondsToLoad property
|
|
||||||
*/
|
|
||||||
function doTrackingFirstPlay(e: Event, data: any) {
|
|
||||||
// how long until the video starts
|
|
||||||
let timeToStartVideo = data.secondsToLoad;
|
|
||||||
|
|
||||||
analytics.playerVideoStartedEvent(embedded);
|
|
||||||
|
|
||||||
// convert bytes to bits, and then divide by seconds
|
|
||||||
const contentInBits = Number(claim.value.source.size) * 8;
|
|
||||||
const durationInSeconds = claim.value.video && claim.value.video.duration;
|
|
||||||
let bitrateAsBitsPerSecond;
|
|
||||||
if (durationInSeconds) {
|
|
||||||
bitrateAsBitsPerSecond = Math.round(contentInBits / durationInSeconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// figure out what server the video is served from and then run start analytic event
|
|
||||||
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
|
|
||||||
// server string such as 'eu-p6'
|
|
||||||
let playerPoweredBy = response.headers.get('x-powered-by') || '';
|
|
||||||
// populates data for watchman, sends prom and matomo event
|
|
||||||
analytics.videoStartEvent(
|
|
||||||
claimId,
|
|
||||||
timeToStartVideo,
|
|
||||||
playerPoweredBy,
|
|
||||||
userId,
|
|
||||||
claim.canonical_url,
|
|
||||||
this,
|
|
||||||
bitrateAsBitsPerSecond
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// hit backend to mark a view
|
|
||||||
doAnalyticsView(uri, timeToStartVideo).then(() => {
|
|
||||||
claimRewards();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const doPlay = useCallback(
|
const doPlay = useCallback(
|
||||||
(playUri) => {
|
(playUri) => {
|
||||||
setDoNavigate(false);
|
setDoNavigate(false);
|
||||||
|
@ -292,7 +248,6 @@ function VideoViewer(props: Props) {
|
||||||
// MORE ON PLAY STUFF
|
// MORE ON PLAY STUFF
|
||||||
function onPlay(player) {
|
function onPlay(player) {
|
||||||
setEnded(false);
|
setEnded(false);
|
||||||
setIsLoading(false);
|
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
setShowAutoplayCountdown(false);
|
setShowAutoplayCountdown(false);
|
||||||
setIsEndedEmbed(false);
|
setIsEndedEmbed(false);
|
||||||
|
@ -334,6 +289,20 @@ function VideoViewer(props: Props) {
|
||||||
setEnded(true);
|
setEnded(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function centerPlayButton() {
|
||||||
|
// center play button
|
||||||
|
const playBT = document.getElementsByClassName('vjs-big-play-button')[0];
|
||||||
|
const videoDiv = window.player.children_[0];
|
||||||
|
const controlBar = document.getElementsByClassName('vjs-control-bar')[0];
|
||||||
|
const leftWidth = (videoDiv.offsetWidth - playBT.offsetWidth) / 2 + 'px';
|
||||||
|
const availableHeight = videoDiv.offsetHeight - controlBar.offsetHeight;
|
||||||
|
const topHeight = (availableHeight - playBT.offsetHeight) / 2 + 3 + 'px';
|
||||||
|
|
||||||
|
playBT.style.top = topHeight;
|
||||||
|
playBT.style.left = leftWidth;
|
||||||
|
playBT.style.margin = '0';
|
||||||
|
}
|
||||||
|
|
||||||
const onPlayerReady = useCallback((player: Player, videoNode: any) => {
|
const onPlayerReady = useCallback((player: Player, videoNode: any) => {
|
||||||
if (!embedded) {
|
if (!embedded) {
|
||||||
setVideoNode(videoNode);
|
setVideoNode(videoNode);
|
||||||
|
@ -351,27 +320,40 @@ function VideoViewer(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldPlay = !embedded || autoplayIfEmbedded;
|
// currently not being used, but leaving for time being
|
||||||
// https://blog.videojs.com/autoplay-best-practices-with-video-js/#Programmatic-Autoplay-and-Success-Failure-Detection
|
// const shouldPlay = !embedded || autoplayIfEmbedded;
|
||||||
if (shouldPlay) {
|
// // https://blog.videojs.com/autoplay-best-practices-with-video-js/#Programmatic-Autoplay-and-Success-Failure-Detection
|
||||||
const playPromise = player.play();
|
// if (shouldPlay) {
|
||||||
const timeoutPromise = new Promise((resolve, reject) =>
|
// const playPromise = player.play();
|
||||||
setTimeout(() => reject(PLAY_TIMEOUT_ERROR), PLAY_TIMEOUT_LIMIT)
|
//
|
||||||
);
|
// const timeoutPromise = new Promise((resolve, reject) =>
|
||||||
|
// setTimeout(() => reject(PLAY_TIMEOUT_ERROR), PLAY_TIMEOUT_LIMIT)
|
||||||
Promise.race([playPromise, timeoutPromise]).catch((error) => {
|
// );
|
||||||
if (typeof error === 'object' && error.name && error.name === 'NotAllowedError') {
|
//
|
||||||
if (player.autoplay() && !player.muted()) {
|
// // if user hasn't interacted with document, mute video and play it
|
||||||
// player.muted(true);
|
// Promise.race([playPromise, timeoutPromise]).catch((error) => {
|
||||||
// another version had player.play()
|
// console.log(error);
|
||||||
}
|
// console.log(playPromise);
|
||||||
}
|
//
|
||||||
setIsLoading(false);
|
// const noPermissionError = typeof error === 'object' && error.name && error.name === 'NotAllowedError';
|
||||||
setIsPlaying(false);
|
// const isATimeoutError = error === PLAY_TIMEOUT_ERROR;
|
||||||
});
|
//
|
||||||
}
|
// if (noPermissionError) {
|
||||||
|
// // if (player.paused()) {
|
||||||
setIsLoading(shouldPlay); // if we are here outside of an embed, we're playing
|
// // document.querySelector('.vjs-big-play-button').style.setProperty('display', 'block', 'important');
|
||||||
|
// // }
|
||||||
|
//
|
||||||
|
// centerPlayButton();
|
||||||
|
//
|
||||||
|
// // to turn muted autoplay on
|
||||||
|
// // if (player.autoplay() && !player.muted()) {
|
||||||
|
// // player.muted(true);
|
||||||
|
// // player.play();
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
// setIsPlaying(false);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
// PR: #5535
|
// PR: #5535
|
||||||
// Move the restoration to a later `loadedmetadata` phase to counter the
|
// Move the restoration to a later `loadedmetadata` phase to counter the
|
||||||
|
@ -382,8 +364,6 @@ function VideoViewer(props: Props) {
|
||||||
// used for tracking buffering for watchman
|
// used for tracking buffering for watchman
|
||||||
player.on('tracking:buffered', doTrackingBuffered);
|
player.on('tracking:buffered', doTrackingBuffered);
|
||||||
|
|
||||||
// first play tracking, used for initializing the watchman api
|
|
||||||
player.on('tracking:firstplay', doTrackingFirstPlay);
|
|
||||||
player.on('ended', () => setEnded(true));
|
player.on('ended', () => setEnded(true));
|
||||||
player.on('play', onPlay);
|
player.on('play', onPlay);
|
||||||
player.on('pause', (event) => onPause(event, player));
|
player.on('pause', (event) => onPause(event, player));
|
||||||
|
@ -433,8 +413,6 @@ function VideoViewer(props: Props) {
|
||||||
)}
|
)}
|
||||||
{isEndedEmbed && <FileViewerEmbeddedEnded uri={uri} />}
|
{isEndedEmbed && <FileViewerEmbeddedEnded uri={uri} />}
|
||||||
{embedded && !isEndedEmbed && <FileViewerEmbeddedTitle uri={uri} />}
|
{embedded && !isEndedEmbed && <FileViewerEmbeddedTitle uri={uri} />}
|
||||||
{/* disable this loading behavior because it breaks when player.play() promise hangs */}
|
|
||||||
{isLoading && <LoadingScreen status={__('Loading')} />}
|
|
||||||
|
|
||||||
{!isFetchingAd && adUrl && (
|
{!isFetchingAd && adUrl && (
|
||||||
<>
|
<>
|
||||||
|
@ -489,6 +467,12 @@ function VideoViewer(props: Props) {
|
||||||
playNext={doPlayNext}
|
playNext={doPlayNext}
|
||||||
playPrevious={doPlayPrevious}
|
playPrevious={doPlayPrevious}
|
||||||
embedded={embedded}
|
embedded={embedded}
|
||||||
|
claimValues={claim.value}
|
||||||
|
doAnalyticsView={doAnalyticsView}
|
||||||
|
claimRewards={claimRewards}
|
||||||
|
uri={uri}
|
||||||
|
clearPosition={clearPosition}
|
||||||
|
centerPlayButton={centerPlayButton}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,11 +20,16 @@
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
padding: var(--spacing-m);
|
padding: var(--spacing-m);
|
||||||
background-color: var(--color-ads-background);
|
background-color: var(--color-ads-background);
|
||||||
display: flex;
|
//display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
.ad__container {
|
||||||
|
width: 358px;
|
||||||
|
height: 201px;
|
||||||
|
}
|
||||||
|
|
||||||
> div,
|
> div,
|
||||||
ins {
|
ins {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -51,6 +56,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.ads__claim-text {
|
.ads__claim-text {
|
||||||
|
margin-top: 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -225,3 +225,18 @@ $control-bar-icon-size: 0.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// larger than default spinner for all but smallest devices
|
||||||
|
@media (min-width:680px) {
|
||||||
|
.vjs-loading-spinner {
|
||||||
|
border-radius: 100px;
|
||||||
|
height: 75px;
|
||||||
|
width: 75px;
|
||||||
|
margin: -49px 0 0 -37px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make sure there's no bad side effects of this
|
||||||
|
button.vjs-big-play-button {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
|
@ -124,7 +124,9 @@ function Ads(props: Props) {
|
||||||
// ad shown in the related videos area
|
// ad shown in the related videos area
|
||||||
const videoAd = (
|
const videoAd = (
|
||||||
<div className="ads__claim-item">
|
<div className="ads__claim-item">
|
||||||
<div id={tagNameToUse} className="ads__injected-video" style={{ display: 'none' }} />
|
<div className="ad__container">
|
||||||
|
<div id={tagNameToUse} className="ads__injected-video" style={{ display: 'none' }} />
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classnames('ads__claim-text', {
|
className={classnames('ads__claim-text', {
|
||||||
'ads__claim-text--small': small,
|
'ads__claim-text--small': small,
|
||||||
|
|
Loading…
Reference in a new issue