@gcorevideo/player 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/assets/icons/new/arrow-left.svg +5 -0
  2. package/assets/icons/new/arrow-right.svg +5 -0
  3. package/assets/icons/new/check.svg +5 -0
  4. package/assets/icons/new/close.svg +12 -0
  5. package/assets/icons/new/full.svg +8 -0
  6. package/assets/icons/new/fullscreen-off.svg +14 -0
  7. package/assets/icons/new/fullscreen-on.svg +14 -0
  8. package/assets/icons/new/gear-hd.svg +16 -0
  9. package/assets/icons/new/gear.svg +12 -0
  10. package/assets/icons/new/hd.svg +8 -0
  11. package/assets/icons/new/pause.svg +5 -0
  12. package/assets/icons/new/pip.svg +5 -0
  13. package/assets/icons/new/play.svg +10 -0
  14. package/assets/icons/new/replayleft.svg +5 -0
  15. package/assets/icons/new/replayright.svg +5 -0
  16. package/assets/icons/new/speed.svg +5 -0
  17. package/assets/icons/new/stats.svg +3 -0
  18. package/assets/icons/new/stop.svg +3 -0
  19. package/assets/icons/new/subtitles-off.svg +5 -0
  20. package/assets/icons/new/subtitles-on.svg +6 -0
  21. package/assets/icons/new/volume-max.svg +5 -0
  22. package/assets/icons/new/volume-min.svg +5 -0
  23. package/assets/icons/new/volume-off.svg +5 -0
  24. package/assets/icons/old/cardboard.svg +4 -0
  25. package/assets/icons/old/close-share.svg +13 -0
  26. package/assets/icons/old/close.svg +13 -0
  27. package/assets/icons/old/fb.svg +13 -0
  28. package/assets/icons/old/fullscreen.svg +12 -0
  29. package/assets/icons/old/language.svg +1 -0
  30. package/assets/icons/old/pause.svg +12 -0
  31. package/assets/icons/old/play.svg +12 -0
  32. package/assets/icons/old/quality-arrow.svg +13 -0
  33. package/assets/icons/old/reload.svg +4 -0
  34. package/assets/icons/old/share.svg +13 -0
  35. package/assets/icons/old/sound-off.svg +15 -0
  36. package/assets/icons/old/sound-on.svg +15 -0
  37. package/assets/icons/old/streams.svg +3 -0
  38. package/assets/icons/old/twitter.svg +13 -0
  39. package/assets/icons/old/wn.svg +15 -0
  40. package/assets/icons/standard/01-play.svg +3 -0
  41. package/assets/icons/standard/02-pause.svg +3 -0
  42. package/assets/icons/standard/03-stop.svg +3 -0
  43. package/assets/icons/standard/04-volume.svg +3 -0
  44. package/assets/icons/standard/05-mute.svg +3 -0
  45. package/assets/icons/standard/06-expand.svg +3 -0
  46. package/assets/icons/standard/07-shrink.svg +3 -0
  47. package/assets/icons/standard/08-hd.svg +3 -0
  48. package/assets/icons/standard/09-cc.svg +8 -0
  49. package/assets/icons/standard/10-reload.svg +4 -0
  50. package/assets/style/main.scss +50 -0
  51. package/assets/style/theme.scss +42 -0
  52. package/assets/style/variables.scss +7 -0
  53. package/dist/DashPlayback-6wKK0_pL.js +666 -0
  54. package/dist/DashPlayback-8U6_s4Jc.js +666 -0
  55. package/dist/DashPlayback-BeZz7mN9.js +663 -0
  56. package/dist/DashPlayback-CRdja67F.js +667 -0
  57. package/dist/DashPlayback-D0df6zGg.js +663 -0
  58. package/dist/DashPlayback-D7egS-CZ.js +664 -0
  59. package/dist/DashPlayback-DH5lZMRR.js +663 -0
  60. package/dist/DashPlayback-DZfIc9sK.js +665 -0
  61. package/dist/DashPlayback-VhCxbQhn.js +666 -0
  62. package/dist/HlsPlayback-Avwy8-0O.js +749 -0
  63. package/dist/index.css +125 -0
  64. package/dist/index.js +467 -0
  65. package/lib/Player.d.ts +50 -0
  66. package/lib/Player.d.ts.map +1 -0
  67. package/lib/Player.js +310 -0
  68. package/lib/backend.d.ts +3 -0
  69. package/lib/backend.d.ts.map +1 -0
  70. package/lib/backend.js +10 -0
  71. package/lib/constants.d.ts +19 -0
  72. package/lib/constants.d.ts.map +1 -0
  73. package/lib/constants.js +18 -0
  74. package/lib/index.d.ts +10 -0
  75. package/lib/index.d.ts.map +1 -0
  76. package/lib/index.js +9 -0
  77. package/lib/internal.types.d.ts +105 -0
  78. package/lib/internal.types.d.ts.map +1 -0
  79. package/lib/internal.types.js +1 -0
  80. package/lib/playback.types.d.ts +13 -0
  81. package/lib/playback.types.d.ts.map +1 -0
  82. package/lib/playback.types.js +1 -0
  83. package/lib/plugins/audio-selector/AudioSelector.d.ts +48 -0
  84. package/lib/plugins/audio-selector/AudioSelector.d.ts.map +1 -0
  85. package/lib/plugins/audio-selector/AudioSelector.js +282 -0
  86. package/lib/plugins/big-mute-button/BigMuteButton.d.ts +33 -0
  87. package/lib/plugins/big-mute-button/BigMuteButton.d.ts.map +1 -0
  88. package/lib/plugins/big-mute-button/BigMuteButton.js +148 -0
  89. package/lib/plugins/bottom-gear/BottomGear.d.ts +30 -0
  90. package/lib/plugins/bottom-gear/BottomGear.d.ts.map +1 -0
  91. package/lib/plugins/bottom-gear/BottomGear.js +103 -0
  92. package/lib/plugins/click-to-pause/ClickToPause.d.ts +16 -0
  93. package/lib/plugins/click-to-pause/ClickToPause.d.ts.map +1 -0
  94. package/lib/plugins/click-to-pause/ClickToPause.js +73 -0
  95. package/lib/plugins/dash-playback/DashPlayback.d.ts +81 -0
  96. package/lib/plugins/dash-playback/DashPlayback.d.ts.map +1 -0
  97. package/lib/plugins/dash-playback/DashPlayback.js +658 -0
  98. package/lib/plugins/dash-plugin/DashPlayback.d.ts +86 -0
  99. package/lib/plugins/dash-plugin/DashPlayback.d.ts.map +1 -0
  100. package/lib/plugins/dash-plugin/DashPlayback.js +659 -0
  101. package/lib/plugins/disable-controls/DisableControls.d.ts +15 -0
  102. package/lib/plugins/disable-controls/DisableControls.d.ts.map +1 -0
  103. package/lib/plugins/disable-controls/DisableControls.js +69 -0
  104. package/lib/plugins/dvr-controls/DVRControls.d.ts +27 -0
  105. package/lib/plugins/dvr-controls/DVRControls.d.ts.map +1 -0
  106. package/lib/plugins/dvr-controls/DVRControls.js +110 -0
  107. package/lib/plugins/hls-playback/HlsPlayback.d.ts +102 -0
  108. package/lib/plugins/hls-playback/HlsPlayback.d.ts.map +1 -0
  109. package/lib/plugins/hls-playback/HlsPlayback.js +747 -0
  110. package/lib/plugins/level-selector/LevelSelector.d.ts +48 -0
  111. package/lib/plugins/level-selector/LevelSelector.d.ts.map +1 -0
  112. package/lib/plugins/level-selector/LevelSelector.js +287 -0
  113. package/lib/plugins/media-control/MediaControl.d.ts +186 -0
  114. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -0
  115. package/lib/plugins/media-control/MediaControl.js +1000 -0
  116. package/lib/plugins/poster/Poster.d.ts +41 -0
  117. package/lib/plugins/poster/Poster.d.ts.map +1 -0
  118. package/lib/plugins/poster/Poster.js +186 -0
  119. package/lib/trace/LogTracer.d.ts +12 -0
  120. package/lib/trace/LogTracer.d.ts.map +1 -0
  121. package/lib/trace/LogTracer.js +17 -0
  122. package/lib/trace/SentryTracer.d.ts +11 -0
  123. package/lib/trace/SentryTracer.d.ts.map +1 -0
  124. package/lib/trace/SentryTracer.js +18 -0
  125. package/lib/trace/Tracer.d.ts +13 -0
  126. package/lib/trace/Tracer.d.ts.map +1 -0
  127. package/lib/trace/Tracer.js +15 -0
  128. package/lib/trace/index.d.ts +18 -0
  129. package/lib/trace/index.d.ts.map +1 -0
  130. package/lib/trace/index.js +27 -0
  131. package/lib/trace/types.d.ts +8 -0
  132. package/lib/trace/types.d.ts.map +1 -0
  133. package/lib/trace/types.js +1 -0
  134. package/lib/types.d.ts +82 -0
  135. package/lib/types.d.ts.map +1 -0
  136. package/lib/types.js +1 -0
  137. package/lib/utils/Logger.d.ts +23 -0
  138. package/lib/utils/Logger.d.ts.map +1 -0
  139. package/lib/utils/Logger.js +81 -0
  140. package/lib/utils/canAutoplay.d.ts +6 -0
  141. package/lib/utils/canAutoplay.d.ts.map +1 -0
  142. package/lib/utils/canAutoplay.js +30 -0
  143. package/lib/utils/errors.d.ts +2 -0
  144. package/lib/utils/errors.d.ts.map +1 -0
  145. package/lib/utils/errors.js +6 -0
  146. package/lib/utils/queryParams.d.ts +2 -0
  147. package/lib/utils/queryParams.d.ts.map +1 -0
  148. package/lib/utils/queryParams.js +4 -0
  149. package/lib/utils/scripts-load.d.ts +2 -0
  150. package/lib/utils/scripts-load.d.ts.map +1 -0
  151. package/lib/utils/scripts-load.js +20 -0
  152. package/lib/utils/types.d.ts +4 -0
  153. package/lib/utils/types.d.ts.map +1 -0
  154. package/lib/utils/types.js +1 -0
  155. package/lib/utils/utils.d.ts +7 -0
  156. package/lib/utils/utils.d.ts.map +1 -0
  157. package/lib/utils/utils.js +57 -0
  158. package/package.json +57 -0
  159. package/rollup.config.js +34 -0
  160. package/src/Player.ts +390 -0
  161. package/src/backend.ts +12 -0
  162. package/src/constants.ts +17 -0
  163. package/src/index.ts +9 -0
  164. package/src/internal.types.ts +126 -0
  165. package/src/playback.types.ts +15 -0
  166. package/src/plugins/dash-playback/DashPlayback.ts +808 -0
  167. package/src/plugins/dash-playback/_DashPlayback.js +688 -0
  168. package/src/plugins/hls-playback/HlsPlayback.ts +909 -0
  169. package/src/plugins/hls-playback/hls.js +706 -0
  170. package/src/trace/LogTracer.ts +23 -0
  171. package/src/trace/SentryTracer.ts +18 -0
  172. package/src/trace/Tracer.ts +27 -0
  173. package/src/trace/index.ts +32 -0
  174. package/src/trace/types.ts +7 -0
  175. package/src/types.ts +100 -0
  176. package/src/typings/@clappr/core/error_mixin.d.ts +15 -0
  177. package/src/typings/@clappr/core/events.d.ts +7 -0
  178. package/src/typings/@clappr/core/html5_video.d.ts +28 -0
  179. package/src/typings/@clappr/core/playback.d.ts +5 -0
  180. package/src/typings/@clappr/core/player.d.ts +83 -0
  181. package/src/typings/@clappr/plugins.d.ts +29 -0
  182. package/src/typings/clappr-zepto.xd.xts +44 -0
  183. package/src/typings/globals.d.ts +8 -0
  184. package/src/utils/Logger.ts +107 -0
  185. package/src/utils/canAutoplay.ts +39 -0
  186. package/src/utils/errors.ts +6 -0
  187. package/src/utils/queryParams.ts +5 -0
  188. package/src/utils/scripts-load.ts +26 -0
  189. package/src/utils/types.ts +5 -0
  190. package/src/utils/utils.ts +64 -0
  191. package/tsconfig.json +43 -0
  192. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,688 @@
1
+ // Copyright 2014 Globo.com Player authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style
3
+ // license that can be found in the LICENSE file.
4
+
5
+ import { Events, HTML5Video, Log, Playback, PlayerError, Utils } from '@clappr/core';
6
+ import DASHJS from 'dashjs';
7
+
8
+ const AUTO = -1;
9
+
10
+ const { now } = Utils;
11
+
12
+ export default class DashPlayback extends HTML5Video {
13
+ get name() {
14
+ return 'dash';
15
+ }
16
+
17
+ get levels() {
18
+ return this._levels || [];
19
+ }
20
+
21
+ get currentLevel() {
22
+ if (this._currentLevel === null || this._currentLevel === undefined) {
23
+ return AUTO;
24
+ } else {
25
+ return this._currentLevel;
26
+ } //0 is a valid level ID
27
+ }
28
+
29
+ get isReady() {
30
+ return this._isReadyState;
31
+ }
32
+
33
+ set currentLevel(id) {
34
+ this._currentLevel = id;
35
+
36
+ this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START);
37
+ const cfg = {
38
+ streaming: {
39
+ abr: {
40
+ autoSwitchBitrate: {
41
+ video: id === -1,
42
+ },
43
+ ABRStrategy: 'abrL2A'
44
+ }
45
+ },
46
+ };
47
+
48
+ this.options.dash && this._dash.updateSettings({ ...this.options.dash, ...cfg });
49
+ if (id !== -1) {
50
+ this._dash.setQualityFor('video', id);
51
+ }
52
+ if (this._playbackType === Playback.VOD) {
53
+ const curr_time = this._dash.time();
54
+
55
+ this.startChangeQuality = true;
56
+ this._dash.seek(0);
57
+ setTimeout(() => {
58
+ this._dash.seek(curr_time);
59
+ this._dash.play();
60
+ this.startChangeQuality = false;
61
+ }, 100);
62
+ }
63
+ }
64
+
65
+ get _startTime() {
66
+ if (this._playbackType === Playback.LIVE && this._playlistType !== 'EVENT') {
67
+ return this._extrapolatedStartTime;
68
+ }
69
+
70
+ return this._playableRegionStartTime;
71
+ }
72
+
73
+ get _now() {
74
+ return now();
75
+ }
76
+
77
+ // the time in the video element which should represent the start of the sliding window
78
+ // extrapolated to increase in real time (instead of jumping as the early segments are removed)
79
+ get _extrapolatedStartTime() {
80
+ if (!this._localStartTimeCorrelation) {
81
+ return this._playableRegionStartTime;
82
+ }
83
+
84
+ const corr = this._localStartTimeCorrelation;
85
+ const timePassed = this._now - corr.local;
86
+ const extrapolatedWindowStartTime = (corr.remote + timePassed) / 1000;
87
+
88
+ // cap at the end of the extrapolated window duration
89
+ return Math.min(extrapolatedWindowStartTime, this._playableRegionStartTime + this._extrapolatedWindowDuration);
90
+ }
91
+
92
+ // the time in the video element which should represent the end of the content
93
+ // extrapolated to increase in real time (instead of jumping as segments are added)
94
+ get _extrapolatedEndTime() {
95
+ const actualEndTime = this._playableRegionStartTime + this._playableRegionDuration;
96
+
97
+ if (!this._localEndTimeCorrelation) {
98
+ return actualEndTime;
99
+ }
100
+
101
+ const corr = this._localEndTimeCorrelation;
102
+ const timePassed = this._now - corr.local;
103
+ const extrapolatedEndTime = (corr.remote + timePassed) / 1000;
104
+
105
+ return Math.max(actualEndTime - this._extrapolatedWindowDuration, Math.min(extrapolatedEndTime, actualEndTime));
106
+ }
107
+
108
+ get _duration() {
109
+ if (!this._dash) {
110
+ return null;
111
+ }
112
+
113
+ return this._dash.duration();
114
+ }
115
+
116
+ constructor(...args) {
117
+ super(...args);
118
+ // backwards compatibility (TODO: remove on 0.3.0)
119
+ this.options.playback || (this.options.playback = this.options);
120
+ // The size of the start time extrapolation window measured as a multiple of segments.
121
+ // Should be 2 or higher, or 0 to disable. Should only need to be increased above 2 if more than one segment is
122
+ // removed from the start of the playlist at a time. E.g if the playlist is cached for 10 seconds and new chunks are
123
+ // added/removed every 5.
124
+ this._extrapolatedWindowNumSegments = this.options.playback?.extrapolatedWindowNumSegments ?? 2;
125
+
126
+ this._playbackType = this.options.playbackType || Playback.VOD;
127
+ this._lastTimeUpdate = { current: 0, total: 0 };
128
+ this._lastDuration = null;
129
+ // for hls streams which have dvr with a sliding window,
130
+ // the content at the start of the playlist is removed as new
131
+ // content is appended at the end.
132
+ // this means the actual playable start time will increase as the
133
+ // start content is deleted
134
+ // For streams with dvr where the entire recording is kept from the
135
+ // beginning this should stay as 0
136
+ this._playableRegionStartTime = 0;
137
+ // {local, remote} remote is the time in the video element that should represent 0
138
+ // local is the system time when the 'remote' measurment took place
139
+ this._localStartTimeCorrelation = null;
140
+ // {local, remote} remote is the time in the video element that should represents the end
141
+ // local is the system time when the 'remote' measurment took place
142
+ this._localEndTimeCorrelation = null;
143
+ // if content is removed from the beginning then this empty area should
144
+ // be ignored. "playableRegionDuration" excludes the empty area
145
+ this._playableRegionDuration = 0;
146
+ // #EXT-X-PROGRAM-DATE-TIME
147
+ this._programDateTime = 0;
148
+
149
+ this.manifestInfo = null;
150
+ // true when the actual duration is longer than hlsjs's live sync point
151
+ // when this is false playableRegionDuration will be the actual duration
152
+ // when this is true playableRegionDuration will exclude the time after the sync point
153
+ this._durationExcludesAfterLiveSyncPoint = false;
154
+ // #EXT-X-TARGETDURATION
155
+ this._segmentTargetDuration = null;
156
+ // #EXT-X-PLAYLIST-TYPE
157
+ this._playlistType = null;
158
+ this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts || 16;
159
+ }
160
+
161
+ _setup() {
162
+ this._dash = DASHJS.MediaPlayer().create();
163
+ this._dash.initialize();
164
+
165
+ const cfg = this.options.dash ?? {};
166
+
167
+ cfg.streaming = cfg.streaming || {};
168
+ cfg.streaming.text = cfg.streaming.text || { defaultEnabled: false };
169
+
170
+ this.options.dash && this._dash.updateSettings(cfg);
171
+
172
+ this._dash.attachView(this.el);
173
+
174
+ this._dash.setAutoPlay(false);
175
+ this._dash.attachSource(this.options.src);
176
+
177
+ this._dash.on(DASHJS.MediaPlayer.events.ERROR, this._onDASHJSSError.bind(this));
178
+ this._dash.on(DASHJS.MediaPlayer.events.PLAYBACK_ERROR, this._onDASHJSSError.bind(this));
179
+
180
+ this._dash.on(DASHJS.MediaPlayer.events.STREAM_INITIALIZED, () => {
181
+ const bitrates = this._dash.getBitrateInfoListFor('video');
182
+
183
+ this._updatePlaybackType();
184
+ this._fillLevels(bitrates);
185
+ this._dash.on(DASHJS.MediaPlayer.events.QUALITY_CHANGE_REQUESTED, (evt) => {
186
+ this._onLevelSwitch({ ...this._levels[evt.newQuality] });
187
+ });
188
+ });
189
+
190
+ this._dash.on(DASHJS.MediaPlayer.events.METRIC_ADDED, (e) => {
191
+ // Listen for the first manifest request in order to update player UI
192
+ if (e.metric === 'DVRInfo') {
193
+ // Extract time info
194
+ this.manifestInfo = e.value.manifestInfo;
195
+ }
196
+ });
197
+
198
+ this._dash.on(DASHJS.MediaPlayer.events.PLAYBACK_RATE_CHANGED, () => {
199
+ this.trigger('dash:playback-rate-changed');
200
+ });
201
+ }
202
+
203
+ render() {
204
+ this._ready();
205
+
206
+ return super.render();
207
+ }
208
+
209
+ _ready() {
210
+ this._isReadyState = true;
211
+ this.trigger(Events.PLAYBACK_READY, this.name);
212
+ }
213
+
214
+ _recover(evt, data, error) {
215
+ console.warn('recover', evt, data, error);
216
+ if (!this._recoveredDecodingError) {
217
+ this._recoveredDecodingError = true;
218
+ this._dash.recoverMediaError();
219
+ } else if (!this._recoveredAudioCodecError) {
220
+ this._recoveredAudioCodecError = true;
221
+ this._dash.swapAudioCodec();
222
+ this._dash.recoverMediaError();
223
+ } else {
224
+ Log.error('hlsjs: failed to recover', { evt, data });
225
+ error.level = PlayerError.Levels.FATAL;
226
+ const formattedError = this.createError(error);
227
+
228
+ this.trigger(Events.PLAYBACK_ERROR, formattedError);
229
+ this.stop();
230
+ }
231
+ }
232
+
233
+ // override
234
+ _setupSrc() {
235
+ // this playback manages the src on the video element itself
236
+ }
237
+
238
+ _startTimeUpdateTimer() {
239
+ this._stopTimeUpdateTimer();
240
+ this._timeUpdateTimer = setInterval(() => {
241
+ this._onDurationChange();
242
+ this._onTimeUpdate();
243
+ }, 100);
244
+ }
245
+
246
+ _stopTimeUpdateTimer() {
247
+ clearInterval(this._timeUpdateTimer);
248
+ }
249
+
250
+ getProgramDateTime() {
251
+ return this._programDateTime;
252
+ }
253
+
254
+ // the duration on the video element itself should not be used
255
+ // as this does not necesarily represent the duration of the stream
256
+ // https://github.com/clappr/clappr/issues/668#issuecomment-157036678
257
+ getDuration() {
258
+ return this._duration;
259
+ }
260
+
261
+ getCurrentTime() {
262
+ // e.g. can be < 0 if user pauses near the start
263
+ // eventually they will then be kicked to the end by hlsjs if they run out of buffer
264
+ // before the official start time
265
+ return this._dash ? this._dash.time() : 0;
266
+ }
267
+
268
+ // the time that "0" now represents relative to when playback started
269
+ // for a stream with a sliding window this will increase as content is
270
+ // removed from the beginning
271
+ getStartTimeOffset() {
272
+ return this._startTime;
273
+ }
274
+
275
+ seekPercentage(percentage) {
276
+ let seekTo = this._duration;
277
+
278
+ if (percentage > 0) {
279
+ seekTo = this._duration * (percentage / 100);
280
+ }
281
+
282
+ this.seek(seekTo);
283
+ }
284
+
285
+ seek(time) {
286
+ if (time < 0) {
287
+ // eslint-disable-next-line max-len
288
+ Log.warn('Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.');
289
+ time = this.getDuration();
290
+ }
291
+ this.dvrEnabled && this._updateDvr(time < this.getDuration() - 10);
292
+ this._dash.seek(time);
293
+ }
294
+
295
+ seekToLivePoint() {
296
+ this.seek(this.getDuration());
297
+ }
298
+
299
+ _updateDvr(status) {
300
+ this.trigger(Events.PLAYBACK_DVR, status);
301
+ this.trigger(Events.PLAYBACK_STATS_ADD, { 'dvr': status });
302
+ }
303
+
304
+ _updateSettings() {
305
+ if (this._playbackType === Playback.VOD) {
306
+ this.settings.left = ['playpause', 'position', 'duration'];
307
+ } else if (this.dvrEnabled) {
308
+ this.settings.left = ['playpause'];
309
+ } else {
310
+ this.settings.left = ['playstop'];
311
+ }
312
+
313
+ this.settings.seekEnabled = this.isSeekEnabled();
314
+ this.trigger(Events.PLAYBACK_SETTINGSUPDATE);
315
+ }
316
+
317
+ _onDASHJSSError(event) {
318
+ // only report/handle errors if they are fatal
319
+ // hlsjs should automatically handle non fatal errors
320
+ this._stopTimeUpdateTimer();
321
+ if (event.error === 'capability' && event.event === 'mediasource') {
322
+ // No support for MSE
323
+ const formattedError = this.createError(event.error);
324
+
325
+ this.trigger(Events.PLAYBACK_ERROR, formattedError);
326
+ Log.error('The media cannot be played because it requires a feature ' +
327
+ 'that your browser does not support.');
328
+ } else if (event.error === 'manifestError' && (
329
+ // Manifest type not supported
330
+ (event.event.id === 'createParser') ||
331
+ // Codec(s) not supported
332
+ (event.event.id === 'codec') ||
333
+ // No streams available to stream
334
+ (event.event.id === 'nostreams') ||
335
+ // Error creating Stream object
336
+ (event.event.id === 'nostreamscomposed') ||
337
+ // syntax error parsing the manifest
338
+ (event.event.id === 'parse') ||
339
+ // a stream has multiplexed audio+video
340
+ (event.event.id === 'multiplexedrep')
341
+ )) {
342
+ // These errors have useful error messages, so we forward it on
343
+ const formattedError = this.createError(event.error);
344
+
345
+ this.trigger(Events.PLAYBACK_ERROR, formattedError);
346
+ if (event.error) {
347
+ Log.error(event.error.message);
348
+ }
349
+ } else if (event.error === 'mediasource') {
350
+ // This error happens when dash.js fails to allocate a SourceBuffer
351
+ // OR the underlying video element throws a `MediaError`.
352
+ // If it's a buffer allocation fail, the message states which buffer
353
+ // (audio/video/text) failed allocation.
354
+ // If it's a `MediaError`, dash.js inspects the error object for
355
+ // additional information to append to the error type.
356
+ const formattedError = this.createError(event.error);
357
+
358
+ this.trigger(Events.PLAYBACK_ERROR, formattedError);
359
+ if (event.error) {
360
+ Log.error(event.error.message);
361
+ }
362
+ } else if (event.error === 'capability' && event.event === 'encryptedmedia') {
363
+ // Browser doesn't support EME
364
+
365
+ const formattedError = this.createError(event.error);
366
+
367
+ this.trigger(Events.PLAYBACK_ERROR, formattedError);
368
+ Log.error('The media cannot be played because it requires encryption ' +
369
+ 'that your browser does not support.');
370
+ } else if (event.error === 'key_session') {
371
+ // This block handles pretty much all errors thrown by the
372
+ // encryption subsystem
373
+ const formattedError = this.createError(event.error);
374
+
375
+ this.trigger(Events.PLAYBACK_ERROR, formattedError);
376
+ if (event.error) {
377
+ Log.error(event.error.message);
378
+ }
379
+ } else if (event.error === 'download') {
380
+ const formattedError = this.createError(event.error);
381
+
382
+ this.trigger(Events.PLAYBACK_ERROR, formattedError);
383
+ Log.error('The media playback was aborted because too many consecutive ' +
384
+ 'download errors occurred.');
385
+ } else if (event.error === 'mssError') {
386
+ const formattedError = this.createError(event.error);
387
+
388
+ this.trigger(Events.PLAYBACK_ERROR, formattedError);
389
+ if (event.error) {
390
+ Log.error(event.error.message);
391
+ }
392
+ } else {
393
+ // ignore the error
394
+ if (event.error) {
395
+ const formattedError = this.createError(event.error);
396
+
397
+ this.trigger(Events.PLAYBACK_ERROR, formattedError);
398
+ Log.error(event.error.message);
399
+ }
400
+
401
+ return;
402
+ }
403
+
404
+ // only reset the dash player in 10ms async, so that the rest of the
405
+ // calling function finishes
406
+ setTimeout(() => {
407
+ this._dash.reset();
408
+ }, 10);
409
+ }
410
+
411
+ _onTimeUpdate() {
412
+ if (this.startChangeQuality) {
413
+ return;
414
+ }
415
+ const update = {
416
+ current: this.getCurrentTime(),
417
+ total: this.getDuration(),
418
+ firstFragDateTime: this.getProgramDateTime()
419
+ };
420
+ const isSame = this._lastTimeUpdate && (
421
+ update.current === this._lastTimeUpdate.current &&
422
+ update.total === this._lastTimeUpdate.total);
423
+
424
+ if (isSame) {
425
+ return;
426
+ }
427
+ this._lastTimeUpdate = update;
428
+ this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name);
429
+ }
430
+
431
+ _onDurationChange() {
432
+ const duration = this.getDuration();
433
+
434
+ if (this._lastDuration === duration) {
435
+ return;
436
+ }
437
+
438
+ this._lastDuration = duration;
439
+ super._onDurationChange();
440
+ }
441
+
442
+ get dvrEnabled() {
443
+ return this._dash?.getDVRWindowSize() >= this._minDvrSize && this.getPlaybackType() === Playback.LIVE;
444
+ }
445
+
446
+ _onProgress() {
447
+ if (!this._dash) {
448
+ return;
449
+ }
450
+
451
+ let buffer = this._dash.getDashMetrics().getCurrentBufferLevel('video', true);
452
+
453
+ if (!buffer) {
454
+ buffer = this._dash.getDashMetrics().getCurrentBufferLevel('audio', true);
455
+ }
456
+ const progress = {
457
+ start: this.getCurrentTime(),
458
+ current: this.getCurrentTime() + buffer,
459
+ total: this.getDuration()
460
+ };
461
+
462
+ this.trigger(Events.PLAYBACK_PROGRESS, progress, {});
463
+ }
464
+
465
+ play() {
466
+ if (!this._dash) {
467
+ this._setup();
468
+ }
469
+
470
+ super.play();
471
+ this._startTimeUpdateTimer();
472
+ }
473
+
474
+ pause() {
475
+ if (!this._dash) {
476
+ return;
477
+ }
478
+
479
+ super.pause();
480
+ if (this.dvrEnabled) {
481
+ this._updateDvr(true);
482
+ }
483
+ }
484
+
485
+ stop() {
486
+ if (this._dash) {
487
+ this._stopTimeUpdateTimer();
488
+ this._dash.reset();
489
+ super.stop();
490
+ delete this._dash;
491
+ }
492
+ }
493
+
494
+ destroy() {
495
+ this._stopTimeUpdateTimer();
496
+ if (this._dash) {
497
+ this._dash.off(DASHJS.MediaPlayer.events.ERROR, this._onDASHJSSError);
498
+ this._dash.off(DASHJS.MediaPlayer.events.MANIFEST_LOADED, this.getDuration);
499
+ this._dash.reset();
500
+ }
501
+ delete this._dash;
502
+ super.destroy();
503
+ }
504
+
505
+ _updatePlaybackType() {
506
+ this._playbackType = this._dash.isDynamic() ? Playback.LIVE : Playback.VOD;
507
+ }
508
+
509
+ _fillLevels(levels) {
510
+ this._levels = levels.map((level) => {
511
+ return { id: level.qualityIndex, level: level };
512
+ });
513
+ this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels);
514
+ }
515
+
516
+ _onLevelUpdated(evt, data) {
517
+ this._segmentTargetDuration = data.details.targetduration;
518
+ this._playlistType = data.details.type || null;
519
+
520
+ let startTimeChanged = false;
521
+ let durationChanged = false;
522
+ const fragments = data.details.fragments;
523
+ const previousPlayableRegionStartTime = this._playableRegionStartTime;
524
+ const previousPlayableRegionDuration = this._playableRegionDuration;
525
+
526
+ if (fragments.length === 0) {
527
+ return;
528
+ }
529
+
530
+ // #EXT-X-PROGRAM-DATE-TIME
531
+ if (fragments[0].rawProgramDateTime) {
532
+ this._programDateTime = fragments[0].rawProgramDateTime;
533
+ }
534
+
535
+ if (this._playableRegionStartTime !== fragments[0].start) {
536
+ startTimeChanged = true;
537
+ this._playableRegionStartTime = fragments[0].start;
538
+ }
539
+
540
+ if (startTimeChanged) {
541
+ if (!this._localStartTimeCorrelation) {
542
+ // set the correlation to map to middle of the extrapolation window
543
+ this._localStartTimeCorrelation = {
544
+ local: this._now,
545
+ remote: (fragments[0].start + (this._extrapolatedWindowDuration / 2)) * 1000
546
+ };
547
+ } else {
548
+ // check if the correlation still works
549
+ const corr = this._localStartTimeCorrelation;
550
+ const timePassed = this._now - corr.local;
551
+ // this should point to a time within the extrapolation window
552
+ const startTime = (corr.remote + timePassed) / 1000;
553
+
554
+ if (startTime < fragments[0].start) {
555
+ // our start time is now earlier than the first chunk
556
+ // (maybe the chunk was removed early)
557
+ // reset correlation so that it sits at the beginning of the first available chunk
558
+ this._localStartTimeCorrelation = {
559
+ local: this._now,
560
+ remote: fragments[0].start * 1000
561
+ };
562
+ } else if (startTime > previousPlayableRegionStartTime + this._extrapolatedWindowDuration) {
563
+ // start time was past the end of the old extrapolation window (so would have been capped)
564
+ // see if now that time would be inside the window, and if it would be set the correlation
565
+ // so that it resumes from the time it was at at the end of the old window
566
+ // update the correlation so that the time starts counting again from the value it's on now
567
+ this._localStartTimeCorrelation = {
568
+ local: this._now,
569
+ remote: Math.max(
570
+ fragments[0].start,
571
+ previousPlayableRegionStartTime + this._extrapolatedWindowDuration
572
+ ) * 1000
573
+ };
574
+ }
575
+ }
576
+ }
577
+
578
+ let newDuration = data.details.totalduration;
579
+
580
+ // if it's a live stream then shorten the duration to remove access
581
+ // to the area after hlsjs's live sync point
582
+ // seeks to areas after this point sometimes have issues
583
+ if (this._playbackType === Playback.LIVE) {
584
+ const fragmentTargetDuration = data.details.targetduration;
585
+ const hlsjsConfig = this.options.playback.hlsjsConfig || {};
586
+ // eslint-disable-next-line no-undef
587
+ const liveSyncDurationCount = hlsjsConfig.liveSyncDurationCount || HLSJS.DefaultConfig.liveSyncDurationCount;
588
+ const hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount;
589
+
590
+ if (hiddenAreaDuration <= newDuration) {
591
+ newDuration -= hiddenAreaDuration;
592
+ this._durationExcludesAfterLiveSyncPoint = true;
593
+ } else {
594
+ this._durationExcludesAfterLiveSyncPoint = false;
595
+ }
596
+ }
597
+
598
+ if (newDuration !== this._playableRegionDuration) {
599
+ durationChanged = true;
600
+ this._playableRegionDuration = newDuration;
601
+ }
602
+
603
+ // Note the end time is not the playableRegionDuration
604
+ // The end time will always increase even if content is removed from the beginning
605
+ const endTime = fragments[0].start + newDuration;
606
+ const previousEndTime = previousPlayableRegionStartTime + previousPlayableRegionDuration;
607
+ const endTimeChanged = endTime !== previousEndTime;
608
+
609
+ if (endTimeChanged) {
610
+ if (!this._localEndTimeCorrelation) {
611
+ // set the correlation to map to the end
612
+ this._localEndTimeCorrelation = {
613
+ local: this._now,
614
+ remote: endTime * 1000
615
+ };
616
+ } else {
617
+ // check if the correlation still works
618
+ const corr = this._localEndTimeCorrelation;
619
+ const timePassed = this._now - corr.local;
620
+ // this should point to a time within the extrapolation window from the end
621
+ const extrapolatedEndTime = (corr.remote + timePassed) / 1000;
622
+
623
+ if (extrapolatedEndTime > endTime) {
624
+ this._localEndTimeCorrelation = {
625
+ local: this._now,
626
+ remote: endTime * 1000
627
+ };
628
+ } else if (extrapolatedEndTime < endTime - this._extrapolatedWindowDuration) {
629
+ // our extrapolated end time is now earlier than the extrapolation window from the actual end time
630
+ // (maybe a chunk became available early)
631
+ // reset correlation so that it sits at the beginning of the extrapolation window from the end time
632
+ this._localEndTimeCorrelation = {
633
+ local: this._now,
634
+ remote: (endTime - this._extrapolatedWindowDuration) * 1000
635
+ };
636
+ } else if (extrapolatedEndTime > previousEndTime) {
637
+ // end time was past the old end time (so would have been capped)
638
+ // set the correlation so that it resumes from the time it was at at the end of the old window
639
+ this._localEndTimeCorrelation = {
640
+ local: this._now,
641
+ remote: previousEndTime * 1000
642
+ };
643
+ }
644
+ }
645
+ }
646
+
647
+ // now that the values have been updated call any methods that use on them so they get the updated values
648
+ // immediately
649
+ durationChanged && this._onDurationChange();
650
+ startTimeChanged && this._onProgress();
651
+ }
652
+
653
+ _onFragmentLoaded(evt, data) {
654
+ this.trigger(Events.PLAYBACK_FRAGMENT_LOADED, data);
655
+ }
656
+
657
+ _onLevelSwitch(evt) {
658
+ const currentLevel = evt;
659
+
660
+ if (currentLevel.level) {
661
+ if (currentLevel) {
662
+ this.trigger(Events.PLAYBACK_BITRATE, {
663
+ height: currentLevel.level.height,
664
+ width: currentLevel.level.width,
665
+ bitrate: currentLevel.level.bitrate,
666
+ level: currentLevel.level.qualityIndex
667
+ });
668
+ }
669
+ }
670
+ }
671
+
672
+ getPlaybackType() {
673
+ return this._playbackType;
674
+ }
675
+
676
+ isSeekEnabled() {
677
+ return (this._playbackType === Playback.VOD || this.dvrEnabled);
678
+ }
679
+ }
680
+
681
+ DashPlayback.canPlay = function (resource, mimeType) {
682
+ const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || [];
683
+ const isDash = ((resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'mpd') ||
684
+ mimeType === 'application/dash+xml' || mimeType === 'video/mp4');
685
+ const isSupportByBrowser = typeof (window.MediaSource || window.WebKitMediaSource) === 'function';
686
+
687
+ return !!(isSupportByBrowser && isDash);
688
+ };