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/**
|
||||
web/dist/**
|
||||
|
||||
ui/component/viewers/videoViewer/internal/plugins/canAutoplay.js
|
||||
|
|
|
@ -63,7 +63,7 @@ type Analytics = {
|
|||
tagFollowEvent: (string, boolean, ?string) => void,
|
||||
playerLoadedEvent: (string, ?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,
|
||||
videoBufferEvent: (
|
||||
StreamClaim,
|
||||
|
|
|
@ -9,9 +9,9 @@ import * as COLLECTIONS_CONSTS from 'constants/collections';
|
|||
import {
|
||||
doChangeVolume,
|
||||
doChangeMute,
|
||||
doAnalyticsView,
|
||||
doAnalyticsBuffer,
|
||||
doAnaltyicsPurchaseEvent,
|
||||
doAnalyticsView,
|
||||
} from 'redux/actions/app';
|
||||
import { selectVolume, selectMute } from 'redux/selectors/app';
|
||||
import { savePosition, clearPosition, doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
||||
|
@ -76,9 +76,7 @@ const perform = (dispatch) => ({
|
|||
savePosition: (uri, position) => dispatch(savePosition(uri, position)),
|
||||
clearPosition: (uri) => dispatch(clearPosition(uri)),
|
||||
changeMute: (muted) => dispatch(doChangeMute(muted)),
|
||||
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||
doAnalyticsBuffer: (uri, bufferData) => dispatch(doAnalyticsBuffer(uri, bufferData)),
|
||||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||
toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()),
|
||||
toggleAutoplayNext: () => dispatch(toggleAutoplayNext()),
|
||||
setVideoPlaybackRate: (rate) => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)),
|
||||
|
@ -95,6 +93,8 @@ const perform = (dispatch) => ({
|
|||
),
|
||||
dispatch(doSetPlayingUri({ uri, collectionId }))
|
||||
),
|
||||
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||
});
|
||||
|
||||
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
|
||||
import { useEffect } from 'react';
|
||||
import analytics from 'analytics';
|
||||
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
|
||||
|
@ -25,6 +26,14 @@ const VideoJsEvents = ({
|
|||
playerRef,
|
||||
autoplaySetting,
|
||||
replay,
|
||||
claimId,
|
||||
userId,
|
||||
claimValues,
|
||||
embedded,
|
||||
uri,
|
||||
doAnalyticsView,
|
||||
claimRewards,
|
||||
playerServerRef,
|
||||
}: {
|
||||
tapToUnmuteRef: any, // DOM element
|
||||
tapToRetryRef: any, // DOM element
|
||||
|
@ -33,7 +42,56 @@ const VideoJsEvents = ({
|
|||
playerRef: any, // DOM element
|
||||
autoplaySetting: 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:
|
||||
// 1. Add keyboard shortcut to the tool-tip.
|
||||
// 2. Override videojs' i18n and use our own (don't want to have 2 systems).
|
||||
|
@ -93,6 +151,10 @@ const VideoJsEvents = ({
|
|||
|
||||
function onInitialPlay() {
|
||||
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)) {
|
||||
// The css starts as "hidden". We make it visible here without
|
||||
// re-rendering the whole thing.
|
||||
|
@ -223,6 +285,20 @@ const VideoJsEvents = ({
|
|||
player.on('volumechange', resolveCtrlText);
|
||||
player.on('volumechange', onVolumeChange);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,48 +1,9 @@
|
|||
// @flow
|
||||
const VideoJsFunctions = ({
|
||||
source,
|
||||
sourceType,
|
||||
videoJsOptions,
|
||||
isAudio,
|
||||
}: {
|
||||
source: string,
|
||||
sourceType: string,
|
||||
videoJsOptions: Object,
|
||||
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
|
||||
// Create the video DOM element and wrapper
|
||||
function createVideoPlayerDOM(container: any) {
|
||||
|
@ -61,7 +22,6 @@ const VideoJsFunctions = ({
|
|||
}
|
||||
|
||||
return {
|
||||
detectFileType,
|
||||
createVideoPlayerDOM,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,7 +4,6 @@ import 'videojs-ima'; // loads directly after contrib-ads
|
|||
import 'video.js/dist/alt/video-js-cdn.min.css';
|
||||
import './plugins/videojs-mobile-ui/plugin';
|
||||
import '@silvermine/videojs-chromecast/dist/silvermine-videojs-chromecast.css';
|
||||
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as OVERLAY from './overlays';
|
||||
import Button from 'component/button';
|
||||
|
@ -22,6 +21,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
|||
import recsys from './plugins/videojs-recsys/plugin';
|
||||
// import runAds from './ads';
|
||||
import videojs from 'video.js';
|
||||
const canAutoplay = require('./plugins/canAutoplay');
|
||||
|
||||
require('@silvermine/videojs-chromecast')(videojs);
|
||||
|
||||
|
@ -73,6 +73,12 @@ type Props = {
|
|||
playNext: () => void,
|
||||
playPrevious: () => 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];
|
||||
|
@ -83,18 +89,6 @@ const IS_IOS =
|
|||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
|
||||
!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')) {
|
||||
videojs.registerPlugin('eventTracking', eventTracking);
|
||||
}
|
||||
|
@ -142,6 +136,12 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
playNext,
|
||||
playPrevious,
|
||||
toggleVideoTheaterMode,
|
||||
claimValues,
|
||||
doAnalyticsView,
|
||||
claimRewards,
|
||||
uri,
|
||||
clearPosition,
|
||||
centerPlayButton,
|
||||
} = props;
|
||||
|
||||
// will later store the videojs player
|
||||
|
@ -151,16 +151,46 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
const tapToUnmuteRef = useRef();
|
||||
const tapToRetryRef = useRef();
|
||||
|
||||
const playerServerRef = useRef();
|
||||
|
||||
// initiate keyboard shortcuts
|
||||
const { curried_function } = keyboardShorcuts({ toggleVideoTheaterMode, playNext, playPrevious });
|
||||
|
||||
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 = {
|
||||
...VIDEO_JS_OPTIONS,
|
||||
preload: 'auto',
|
||||
playbackRates: videoPlaybackRates,
|
||||
responsive: true,
|
||||
controls: true,
|
||||
html5: {
|
||||
vhs: {
|
||||
overrideNative: !videojs.browser.IS_ANY_SAFARI,
|
||||
},
|
||||
},
|
||||
autoplay: autoplay,
|
||||
muted: startMuted,
|
||||
sources: [{ src: source, type: sourceType }],
|
||||
poster: poster, // thumb looks bad in app, and if autoplay, flashing poster is annoying
|
||||
plugins: { eventTracking: true, overlay: OVERLAY.OVERLAY_DATA },
|
||||
// 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 || '',
|
||||
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
|
||||
function initializeVideoPlayer(el) {
|
||||
function initializeVideoPlayer(el, canAutoplayVideo) {
|
||||
if (!el) return;
|
||||
|
||||
const vjs = videojs(el, videoJsOptions, () => {
|
||||
const vjs = videojs(el, videoJsOptions, async () => {
|
||||
const player = playerRef.current;
|
||||
const adapter = new playerjs.VideoJSAdapter(player);
|
||||
|
||||
|
@ -209,6 +228,13 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
// Initialize mobile UI.
|
||||
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);
|
||||
|
||||
// Add quality selector to player
|
||||
|
@ -228,11 +254,32 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
// set playsinline for mobile
|
||||
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
|
||||
const videoNode = containerRef.current && containerRef.current.querySelector('video, audio');
|
||||
|
||||
onPlayerReady(player, videoNode);
|
||||
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)
|
||||
|
@ -245,12 +292,16 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
/** 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.
|
||||
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
|
||||
const vjsPlayer = initializeVideoPlayer(vjsElement);
|
||||
const vjsPlayer = initializeVideoPlayer(vjsElement, canAutoplayVideo);
|
||||
|
||||
// Add reference to player to global scope
|
||||
window.player = vjsPlayer;
|
||||
|
@ -259,7 +310,34 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
playerRef.current = vjsPlayer;
|
||||
|
||||
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
|
||||
return () => {
|
||||
|
@ -275,41 +353,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
|||
window.player = undefined;
|
||||
}
|
||||
};
|
||||
}, [isAudio, source]);
|
||||
|
||||
// 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]);
|
||||
}, [isAudio, source, reload]);
|
||||
|
||||
return (
|
||||
<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 FileViewerEmbeddedEnded from 'web/component/fileViewerEmbeddedEnded';
|
||||
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import { addTheaterModeButton } from './internal/theater-mode';
|
||||
import { addAutoplayNextButton } from './internal/autoplay-next';
|
||||
import { addPlayNextButton } from './internal/play-next';
|
||||
|
@ -28,8 +27,8 @@ import type { HomepageCat } from 'util/buildHomepage';
|
|||
import debounce from 'util/debounce';
|
||||
import { formatLbryUrlForWeb, generateListSearchUrlParams } from 'util/url';
|
||||
|
||||
const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
|
||||
const PLAY_TIMEOUT_LIMIT = 2000;
|
||||
// const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
|
||||
// const PLAY_TIMEOUT_LIMIT = 2000;
|
||||
|
||||
type Props = {
|
||||
position: number,
|
||||
|
@ -45,9 +44,7 @@ type Props = {
|
|||
uri: string,
|
||||
autoplayNext: boolean,
|
||||
autoplayIfEmbedded: boolean,
|
||||
doAnalyticsView: (string, number) => Promise<any>,
|
||||
doAnalyticsBuffer: (string, any) => void,
|
||||
claimRewards: () => void,
|
||||
savePosition: (string, number) => void,
|
||||
clearPosition: (string) => void,
|
||||
toggleVideoTheaterMode: () => void,
|
||||
|
@ -65,6 +62,8 @@ type Props = {
|
|||
previousListUri: string,
|
||||
videoTheaterMode: boolean,
|
||||
isMarkdownOrComment: boolean,
|
||||
doAnalyticsView: (string, number) => void,
|
||||
claimRewards: () => void,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -87,8 +86,8 @@ function VideoViewer(props: Props) {
|
|||
volume,
|
||||
autoplayNext,
|
||||
autoplayIfEmbedded,
|
||||
doAnalyticsView,
|
||||
doAnalyticsBuffer,
|
||||
doAnalyticsView,
|
||||
claimRewards,
|
||||
savePosition,
|
||||
clearPosition,
|
||||
|
@ -133,7 +132,6 @@ function VideoViewer(props: Props) {
|
|||
const [adUrl, setAdUrl, isFetchingAd] = useGetAds(approvedVideo, adsEnabled);
|
||||
/* 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 */
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [replay, setReplay] = useState(false);
|
||||
const [videoNode, setVideoNode] = useState();
|
||||
|
||||
|
@ -150,7 +148,6 @@ function VideoViewer(props: Props) {
|
|||
if (uri && previousUri && uri !== previousUri) {
|
||||
setShowAutoplayCountdown(false);
|
||||
setIsEndedEmbed(false);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [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(
|
||||
(playUri) => {
|
||||
setDoNavigate(false);
|
||||
|
@ -292,7 +248,6 @@ function VideoViewer(props: Props) {
|
|||
// MORE ON PLAY STUFF
|
||||
function onPlay(player) {
|
||||
setEnded(false);
|
||||
setIsLoading(false);
|
||||
setIsPlaying(true);
|
||||
setShowAutoplayCountdown(false);
|
||||
setIsEndedEmbed(false);
|
||||
|
@ -334,6 +289,20 @@ function VideoViewer(props: Props) {
|
|||
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) => {
|
||||
if (!embedded) {
|
||||
setVideoNode(videoNode);
|
||||
|
@ -351,27 +320,40 @@ function VideoViewer(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
const shouldPlay = !embedded || autoplayIfEmbedded;
|
||||
// https://blog.videojs.com/autoplay-best-practices-with-video-js/#Programmatic-Autoplay-and-Success-Failure-Detection
|
||||
if (shouldPlay) {
|
||||
const playPromise = player.play();
|
||||
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()) {
|
||||
// player.muted(true);
|
||||
// another version had player.play()
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
setIsPlaying(false);
|
||||
});
|
||||
}
|
||||
|
||||
setIsLoading(shouldPlay); // if we are here outside of an embed, we're playing
|
||||
// currently not being used, but leaving for time being
|
||||
// const shouldPlay = !embedded || autoplayIfEmbedded;
|
||||
// // https://blog.videojs.com/autoplay-best-practices-with-video-js/#Programmatic-Autoplay-and-Success-Failure-Detection
|
||||
// if (shouldPlay) {
|
||||
// const playPromise = player.play();
|
||||
//
|
||||
// const timeoutPromise = new Promise((resolve, reject) =>
|
||||
// setTimeout(() => reject(PLAY_TIMEOUT_ERROR), PLAY_TIMEOUT_LIMIT)
|
||||
// );
|
||||
//
|
||||
// // if user hasn't interacted with document, mute video and play it
|
||||
// Promise.race([playPromise, timeoutPromise]).catch((error) => {
|
||||
// console.log(error);
|
||||
// console.log(playPromise);
|
||||
//
|
||||
// const noPermissionError = typeof error === 'object' && error.name && error.name === 'NotAllowedError';
|
||||
// const isATimeoutError = error === PLAY_TIMEOUT_ERROR;
|
||||
//
|
||||
// if (noPermissionError) {
|
||||
// // if (player.paused()) {
|
||||
// // 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
|
||||
// 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
|
||||
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('play', onPlay);
|
||||
player.on('pause', (event) => onPause(event, player));
|
||||
|
@ -433,8 +413,6 @@ function VideoViewer(props: Props) {
|
|||
)}
|
||||
{isEndedEmbed && <FileViewerEmbeddedEnded 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 && (
|
||||
<>
|
||||
|
@ -489,6 +467,12 @@ function VideoViewer(props: Props) {
|
|||
playNext={doPlayNext}
|
||||
playPrevious={doPlayPrevious}
|
||||
embedded={embedded}
|
||||
claimValues={claim.value}
|
||||
doAnalyticsView={doAnalyticsView}
|
||||
claimRewards={claimRewards}
|
||||
uri={uri}
|
||||
clearPosition={clearPosition}
|
||||
centerPlayButton={centerPlayButton}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -20,11 +20,16 @@
|
|||
border-bottom: 1px solid var(--color-border);
|
||||
padding: var(--spacing-m);
|
||||
background-color: var(--color-ads-background);
|
||||
display: flex;
|
||||
//display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
|
||||
.ad__container {
|
||||
width: 358px;
|
||||
height: 201px;
|
||||
}
|
||||
|
||||
> div,
|
||||
ins {
|
||||
width: 100%;
|
||||
|
@ -51,6 +56,7 @@
|
|||
}
|
||||
|
||||
.ads__claim-text {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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
|
||||
const videoAd = (
|
||||
<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
|
||||
className={classnames('ads__claim-text', {
|
||||
'ads__claim-text--small': small,
|
||||
|
|
Loading…
Reference in a new issue