1
1
/* @flow */
2
2
3
+ // $FlowIgnore HLS media requires a fairly big dependency, so load it separately on demand
4
+ import 'file-loader?name=dash.mediaplayer.min.js!../../node_modules/dashjs/dist/dash.mediaplayer.min.js' ; // eslint-disable-line import/no-extraneous-dependencies
5
+ /* global dashjs:readonly */
6
+ /*:: import dashjs from 'dashjs' */
7
+
3
8
import $ from 'jquery' ;
4
- import { compact , pull , without , once , memoize , intersection } from 'lodash-es' ;
9
+ import { pull , without , once , memoize , intersection } from 'lodash-es' ;
5
10
import DOMPurify from 'dompurify' ;
6
11
import type {
7
12
ExpandoMedia ,
@@ -14,10 +19,12 @@ import type {
14
19
GenericMedia ,
15
20
} from '../core/host' ;
16
21
import { Host } from '../core/host' ;
22
+ import { loadOptions } from '../core/init' ;
17
23
import { Module } from '../core/module' ;
18
24
import {
19
25
positiveModulo ,
20
26
downcast ,
27
+ filterMap ,
21
28
Thing ,
22
29
SelectedThing ,
23
30
addCSS ,
@@ -30,6 +37,7 @@ import {
30
37
frameThrottle ,
31
38
isPageType ,
32
39
isAppType ,
40
+ stopPageContextScript ,
33
41
string ,
34
42
waitForEvent ,
35
43
watchForElements ,
@@ -44,9 +52,11 @@ import {
44
52
download ,
45
53
isPrivateBrowsing ,
46
54
openNewTab ,
55
+ loadScript ,
47
56
Permissions ,
48
57
Storage ,
49
58
} from '../environment' ;
59
+ import * as Modules from '../core/modules' ;
50
60
import * as Options from '../core/options' ;
51
61
import * as Notifications from './notifications' ;
52
62
import * as SettingsNavigation from './settingsNavigation' ;
@@ -66,6 +76,7 @@ import {
66
76
expandos ,
67
77
activeExpandos ,
68
78
} from './showImages/expando' ;
79
+ import vreddit from './hosts/vreddit' ;
69
80
import __hosts from 'sibling-loader?import=default!./hosts/default' ;
70
81
71
82
const siteModules : Map < string , Host < any , any > > = new Map (
@@ -439,6 +450,25 @@ module.options = {
439
450
return options ;
440
451
} , { } ) ,
441
452
} ;
453
+
454
+ module . onInit = ( ) => {
455
+ if ( isAppType ( 'r2' ) ) {
456
+ // We'll probably replace Reddit's video player, so disable the script containing it for now
457
+ // This happens on `onInit` since RES' options load slower than the script's
458
+ const preventVideoPlayerScriptTasks = [
459
+ stopPageContextScript ( script => ( / ^ \/ ? v i d e o p l a y e r \. / ) . test ( new URL ( script . src , location . origin ) . pathname ) , 'head' ) ,
460
+ // Reddit loads scripts which initializes the video player, which will cause a slowdown if not blocked
461
+ stopPageContextScript ( script => ! ! script . innerHTML . match ( 'RedditVideoPlayer' ) , '#siteTable' ) ,
462
+ ] ;
463
+
464
+ loadOptions . then ( ( ) => {
465
+ // We might need to restore the native player
466
+ const removeNativePlayer = Modules . isRunning ( module ) && isSiteModuleEnabled ( vreddit ) && vreddit . options && vreddit . options . forceReplaceNativeExpando . value ;
467
+ if ( ! removeNativePlayer ) forEachSeq ( preventVideoPlayerScriptTasks , ( { undo } ) => undo ( ) ) ;
468
+ } ) ;
469
+ }
470
+ } ;
471
+
442
472
module . exclude = [
443
473
/ ^ \/ a d s \/ [ \- \w \. _ \? = ] * / i,
444
474
'submit' ,
@@ -833,11 +863,6 @@ async function checkElementForMedia(element: HTMLAnchorElement) {
833
863
834
864
if ( nativeExpando ) {
835
865
trackNativeExpando ( nativeExpando , element , thing ) ;
836
-
837
- if ( nativeExpando . open ) {
838
- console . log ( 'Native expando has already been opened; skipping.' , element . href ) ;
839
- return ;
840
- }
841
866
}
842
867
843
868
if ( thing && thing . isCrosspost ( ) && module . options . crossposts . value === 'none' ) {
@@ -852,17 +877,21 @@ async function checkElementForMedia(element: HTMLAnchorElement) {
852
877
}
853
878
854
879
for ( const siteModule of modulesForHostname ( mediaUrl . hostname ) ) {
855
- if ( nativeExpando ) {
856
- const { options : { replaceNativeExpando } = { } } = siteModule ;
857
- if ( replaceNativeExpando && ! replaceNativeExpando . value ) continue ;
858
- }
859
-
860
880
const detectResult = siteModule . detect ( mediaUrl , thing ) ;
861
881
if ( ! detectResult ) continue ;
862
882
883
+ if ( nativeExpando ) {
884
+ const forceReplaceNativeExpandoOption = siteModule . options && siteModule . options . forceReplaceNativeExpando ;
885
+ if ( nativeExpando . open && ! ( forceReplaceNativeExpandoOption && forceReplaceNativeExpandoOption . value ) ) {
886
+ console . log ( 'Native expando has already been opened; skipping.' , element . href ) ;
887
+ return ;
888
+ }
889
+
890
+ nativeExpando . detach ( ) ;
891
+ }
892
+
863
893
const expando = new Expando ( mediaUrl . href ) ;
864
894
865
- if ( nativeExpando ) nativeExpando . detach ( ) ;
866
895
placeExpando ( expando , element , thing ) ;
867
896
expando . onExpand ( ( ) => { trackMediaLoad ( element , thing ) ; } ) ;
868
897
linksMap . set ( element , expando ) ;
@@ -1089,8 +1118,8 @@ export class Media {
1089
1118
1090
1119
supportsUnload(): boolean { return false; }
1091
1120
_state: 'loaded' | 'unloaded' = 'loaded';
1092
- _unload(): void {}
1093
- _restore(): void {}
1121
+ _unload(): any {}
1122
+ _restore(): any {}
1094
1123
1095
1124
setLoaded(state: boolean) {
1096
1125
if (state) {
@@ -1832,6 +1861,7 @@ class Video extends Media {
1832
1861
time : number ;
1833
1862
frameRate : number ;
1834
1863
useVideoManager : boolean ;
1864
+ dashPlayer : * ;
1835
1865
1836
1866
constructor ( {
1837
1867
title,
@@ -1883,14 +1913,32 @@ class Video extends Media {
1883
1913
}
1884
1914
} ;
1885
1915
1886
- const sourceElements = $ ( compact ( sources . map ( v => {
1887
- if ( ! this . video . canPlayType ( v . type ) ) return null ;
1888
- const source = document . createElement ( 'source' ) ;
1889
- source . src = v . source ;
1890
- source . type = v . type ;
1891
- if ( v . reverse ) source . dataset . reverse = v . reverse ;
1892
- return source ;
1893
- } ) ) ) . appendTo ( this . video ) . get ( ) ;
1916
+ const sourceElements = filterMap ( sources , v => {
1917
+ if ( this . video . canPlayType ( v . type ) ) {
1918
+ const source = document . createElement ( 'source' ) ;
1919
+ source . src = v . source ;
1920
+ source . type = v . type ;
1921
+ if ( v . reverse ) source . dataset . reverse = v . reverse ;
1922
+ return [ source ] ;
1923
+ } else {
1924
+ if ( v . type === 'application/dash+xml' ) {
1925
+ // Use external library
1926
+ this . dashPlayer = loadScript ( '/dash.mediaplayer.min.js' ) . then ( ( ) => {
1927
+ dashjs . skipAutoCreate = true ;
1928
+
1929
+ const player = dashjs . MediaPlayer ( ) . create ( ) ; // eslint-disable-line new-cap
1930
+
1931
+ player . initialize ( ) ;
1932
+ player . attachSource ( v . source ) ;
1933
+ player . preload ( ) ;
1934
+
1935
+ return player ;
1936
+ } ) ;
1937
+
1938
+ return [ document . createElement ( 'span' ) ] ; // Return dummy element as the proper `source` element has side effects
1939
+ }
1940
+ }
1941
+ } ) ;
1894
1942
1895
1943
if ( ! sourceElements . length ) {
1896
1944
if ( fallback ) {
@@ -1906,12 +1954,18 @@ class Video extends Media {
1906
1954
}
1907
1955
}
1908
1956
1957
+ this . video . append ( ...sourceElements ) ;
1958
+
1909
1959
const lastSource = sourceElements [ sourceElements . length - 1 ] ;
1910
1960
lastSource . addEventListener ( 'error' , displayError ) ;
1911
1961
1912
1962
if ( reversed ) this . reverse ( ) ;
1913
1963
1914
- this . ready = Promise . race ( [ waitForEvent ( this . video , 'suspend' ) , waitForEvent ( lastSource , 'error' ) ] ) ;
1964
+ this . ready = Promise . race ( [
1965
+ waitForEvent ( this . video , 'suspend' ) ,
1966
+ waitForEvent ( lastSource , 'error' ) ,
1967
+ waitForEvent ( this . video , 'ended' ) ,
1968
+ ] ) ;
1915
1969
1916
1970
const setPlayIcon = ( ) = > {
1917
1971
if ( ! this . video . paused ) this . element . setAttribute ( 'playing' , '' ) ;
@@ -1960,7 +2014,7 @@ class Video extends Media {
1960
2014
1961
2015
this . setMaxSize ( this . video ) ;
1962
2016
this . makeZoomable ( this . video ) ;
1963
- this . addControls ( this . video , undefined , sources [ 0 ] . source ) ;
2017
+ this . addControls ( this . video , undefined , sourceElements [ 0 ] . getAttribute ( 'src' ) ) ;
1964
2018
this . makeMovable ( container ) ;
1965
2019
this . keepVisible ( container ) ;
1966
2020
this . makeIndependent ( container ) ;
@@ -2082,25 +2136,42 @@ class Video extends Media {
2082
2136
return this.video.paused;
2083
2137
}
2084
2138
2085
- _unload() {
2139
+ async _unload() {
2086
2140
// Video is auto-paused when detached from DOM
2087
2141
if (!this.isAttached()) return;
2088
2142
2089
2143
if (!this.video.paused) this.video.pause();
2090
2144
2091
2145
this.time = this.video.currentTime;
2092
- this.video.setAttribute('src', ''); // this.video.src has precedence over any child source element
2093
- this.video.load();
2146
+
2147
+ const dashPlayer = await this.dashPlayer;
2148
+ if (dashPlayer) {
2149
+ dashPlayer.updateSettings({ streaming: { bufferToKeep: 0, bufferAheadToKeep: 0 } });
2150
+ } else {
2151
+ this.video.setAttribute('src', ''); // this.video.src has precedence over any child source element
2152
+ this.video.load();
2153
+ }
2094
2154
2095
2155
if (this.useVideoManager) mutedVideoManager().unobserve(this.video);
2096
2156
}
2097
2157
2098
- _restore() {
2099
- if (this.video.hasAttribute('src')) {
2158
+ async _restore() {
2159
+ if (!this.dashPlayer && this.video.hasAttribute('src')) {
2100
2160
this.video.removeAttribute('src');
2101
2161
this.video.load();
2102
2162
}
2103
2163
2164
+ const dashPlayer = await this.dashPlayer;
2165
+ if (dashPlayer) {
2166
+ try {
2167
+ /* if this throws an error, video is not attached */
2168
+ dashPlayer.getVideoElement();
2169
+ } catch (err) {
2170
+ dashPlayer.attachView(this.video);
2171
+ }
2172
+ dashPlayer.resetSettings(); // Assumes that the only settings changes are done so in ` _unload `
2173
+ }
2174
+
2104
2175
this.video.currentTime = this.time;
2105
2176
2106
2177
if (this.autoplay) {
0 commit comments