@gcorevideo/player 2.4.0 → 2.4.2
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.
- package/dist/index.js +55 -37
- package/lib/Player.js +1 -1
- package/lib/build.d.ts.map +1 -1
- package/lib/build.js +2 -1
- package/lib/plugins/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/plugins/dash-playback/DashPlayback.js +51 -34
- package/package.json +1 -1
- package/src/Player.ts +1 -1
- package/src/build.ts +2 -1
- package/src/plugins/dash-playback/DashPlayback.ts +348 -258
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -2,208 +2,228 @@
|
|
|
2
2
|
// Use of this source code is governed by a BSD-style
|
|
3
3
|
// license that can be found in the LICENSE file.
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import {
|
|
6
|
+
Events,
|
|
7
|
+
HTML5Video,
|
|
8
|
+
Log,
|
|
9
|
+
Playback,
|
|
10
|
+
Utils,
|
|
11
|
+
} from '@clappr/core'
|
|
12
|
+
import assert from 'assert' // uses Node.js's assert types
|
|
13
|
+
import DASHJS, {
|
|
8
14
|
ErrorEvent as DashErrorEvent,
|
|
9
15
|
PlaybackErrorEvent as DashPlaybackErrorEvent,
|
|
10
16
|
type BitrateInfo,
|
|
11
17
|
MetricEvent as DashMetricEvent,
|
|
12
|
-
IManifestInfo
|
|
13
|
-
} from 'dashjs'
|
|
14
|
-
import { trace } from '../../trace/index.js'
|
|
18
|
+
IManifestInfo,
|
|
19
|
+
} from 'dashjs'
|
|
20
|
+
import { trace } from '../../trace/index.js'
|
|
15
21
|
|
|
16
|
-
import { Duration, TimePosition, TimeValue } from '../../playback.types.js'
|
|
22
|
+
import { Duration, TimePosition, TimeValue } from '../../playback.types.js'
|
|
17
23
|
|
|
18
|
-
const AUTO = -1
|
|
24
|
+
const AUTO = -1
|
|
19
25
|
|
|
20
|
-
const { now } = Utils
|
|
26
|
+
const { now } = Utils
|
|
21
27
|
|
|
22
28
|
type PlaybackType =
|
|
23
29
|
| typeof Playback.VOD
|
|
24
30
|
| typeof Playback.LIVE
|
|
25
31
|
| typeof Playback.AOD
|
|
26
|
-
| typeof Playback.NO_OP
|
|
32
|
+
| typeof Playback.NO_OP
|
|
27
33
|
|
|
28
|
-
type PlaylistType = string
|
|
34
|
+
type PlaylistType = string // TODO union
|
|
29
35
|
|
|
30
36
|
type QualityLevel = {
|
|
31
|
-
id: number
|
|
32
|
-
level: BitrateInfo
|
|
33
|
-
}
|
|
37
|
+
id: number
|
|
38
|
+
level: BitrateInfo
|
|
39
|
+
}
|
|
34
40
|
|
|
35
41
|
type LocalTimeCorrelation = {
|
|
36
|
-
local: number
|
|
37
|
-
remote: number
|
|
42
|
+
local: number
|
|
43
|
+
remote: number
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
const T =
|
|
46
|
+
const T = 'DashPlayback'
|
|
41
47
|
|
|
42
48
|
export default class DashPlayback extends HTML5Video {
|
|
43
|
-
_levels: QualityLevel[] | null = null
|
|
49
|
+
_levels: QualityLevel[] | null = null
|
|
44
50
|
|
|
45
|
-
_currentLevel: number | null = null
|
|
51
|
+
_currentLevel: number | null = null
|
|
46
52
|
|
|
47
|
-
_durationExcludesAfterLiveSyncPoint: boolean = false
|
|
53
|
+
_durationExcludesAfterLiveSyncPoint: boolean = false
|
|
48
54
|
|
|
49
|
-
_isReadyState: boolean = false
|
|
55
|
+
_isReadyState: boolean = false
|
|
50
56
|
|
|
51
|
-
_playableRegionDuration: number = 0
|
|
57
|
+
_playableRegionDuration: number = 0
|
|
52
58
|
|
|
53
|
-
_playableRegionStartTime: number = 0
|
|
59
|
+
_playableRegionStartTime: number = 0
|
|
54
60
|
|
|
55
|
-
_playbackType: PlaybackType = Playback.VOD
|
|
61
|
+
_playbackType: PlaybackType = Playback.VOD
|
|
56
62
|
|
|
57
|
-
_playlistType: PlaylistType | null = null
|
|
63
|
+
_playlistType: PlaylistType | null = null
|
|
58
64
|
|
|
59
65
|
// #EXT-X-PROGRAM-DATE-TIME
|
|
60
|
-
_programDateTime: TimeValue = 0
|
|
66
|
+
_programDateTime: TimeValue = 0
|
|
61
67
|
|
|
62
|
-
_dash: DASHJS.MediaPlayerClass | null = null
|
|
68
|
+
_dash: DASHJS.MediaPlayerClass | null = null
|
|
63
69
|
|
|
64
|
-
_extrapolatedWindowDuration: number = 0
|
|
70
|
+
_extrapolatedWindowDuration: number = 0
|
|
65
71
|
|
|
66
|
-
_extrapolatedWindowNumSegments: number = 0
|
|
72
|
+
_extrapolatedWindowNumSegments: number = 0
|
|
67
73
|
|
|
68
|
-
_lastDuration: Duration | null = null
|
|
74
|
+
_lastDuration: Duration | null = null
|
|
69
75
|
|
|
70
|
-
_lastTimeUpdate: TimePosition = { current: 0, total: 0 }
|
|
76
|
+
_lastTimeUpdate: TimePosition = { current: 0, total: 0 }
|
|
71
77
|
|
|
72
|
-
_localStartTimeCorrelation: LocalTimeCorrelation | null = null
|
|
78
|
+
_localStartTimeCorrelation: LocalTimeCorrelation | null = null
|
|
73
79
|
|
|
74
|
-
_localEndTimeCorrelation: LocalTimeCorrelation | null = null
|
|
80
|
+
_localEndTimeCorrelation: LocalTimeCorrelation | null = null
|
|
75
81
|
|
|
76
|
-
_recoverAttemptsRemaining: number = 0
|
|
82
|
+
_recoverAttemptsRemaining: number = 0
|
|
77
83
|
|
|
78
|
-
_recoveredAudioCodecError = false
|
|
84
|
+
_recoveredAudioCodecError = false
|
|
79
85
|
|
|
80
|
-
_recoveredDecodingError = false
|
|
86
|
+
_recoveredDecodingError = false
|
|
81
87
|
|
|
82
|
-
startChangeQuality = false
|
|
88
|
+
startChangeQuality = false
|
|
83
89
|
|
|
84
|
-
manifestInfo: IManifestInfo | null = null
|
|
90
|
+
manifestInfo: IManifestInfo | null = null
|
|
85
91
|
|
|
86
92
|
// #EXT-X-TARGETDURATION
|
|
87
|
-
_segmentTargetDuration: Duration | null = null
|
|
93
|
+
_segmentTargetDuration: Duration | null = null
|
|
88
94
|
|
|
89
|
-
_timeUpdateTimer: ReturnType<typeof setInterval> | null = null
|
|
95
|
+
_timeUpdateTimer: ReturnType<typeof setInterval> | null = null
|
|
90
96
|
|
|
91
97
|
get name() {
|
|
92
|
-
return 'dash'
|
|
98
|
+
return 'dash'
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
get levels(): QualityLevel[] {
|
|
96
|
-
return this._levels || []
|
|
102
|
+
return this._levels || []
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
get currentLevel(): number {
|
|
100
106
|
if (this._currentLevel === null) {
|
|
101
|
-
return AUTO
|
|
107
|
+
return AUTO
|
|
102
108
|
}
|
|
103
109
|
// 0 is a valid level ID
|
|
104
|
-
return this._currentLevel
|
|
110
|
+
return this._currentLevel
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
get isReady() {
|
|
108
|
-
return this._isReadyState
|
|
114
|
+
return this._isReadyState
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
set currentLevel(id) {
|
|
112
|
-
this._currentLevel = id
|
|
118
|
+
this._currentLevel = id
|
|
113
119
|
|
|
114
|
-
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)
|
|
120
|
+
this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)
|
|
115
121
|
const cfg = {
|
|
116
122
|
streaming: {
|
|
117
123
|
abr: {
|
|
118
124
|
autoSwitchBitrate: {
|
|
119
125
|
video: id === -1,
|
|
120
126
|
},
|
|
121
|
-
ABRStrategy: 'abrL2A'
|
|
122
|
-
}
|
|
127
|
+
ABRStrategy: 'abrL2A',
|
|
128
|
+
},
|
|
123
129
|
},
|
|
124
|
-
}
|
|
130
|
+
}
|
|
125
131
|
|
|
126
|
-
assert.ok(
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
assert.ok(
|
|
133
|
+
this._dash,
|
|
134
|
+
'An instance of dashjs MediaPlayer is required to switch levels',
|
|
135
|
+
)
|
|
136
|
+
const dash = this._dash
|
|
137
|
+
this.options.dash && dash.updateSettings({ ...this.options.dash, ...cfg })
|
|
129
138
|
if (id !== -1) {
|
|
130
|
-
this._dash.setQualityFor('video', id)
|
|
139
|
+
this._dash.setQualityFor('video', id)
|
|
131
140
|
}
|
|
132
141
|
if (this._playbackType === Playback.VOD) {
|
|
133
|
-
const curr_time = this._dash.time()
|
|
142
|
+
const curr_time = this._dash.time()
|
|
134
143
|
|
|
135
|
-
this.startChangeQuality = true
|
|
136
|
-
dash.seek(0)
|
|
144
|
+
this.startChangeQuality = true
|
|
145
|
+
dash.seek(0)
|
|
137
146
|
setTimeout(() => {
|
|
138
|
-
dash.seek(curr_time)
|
|
139
|
-
dash.play()
|
|
140
|
-
this.startChangeQuality = false
|
|
141
|
-
}, 100)
|
|
147
|
+
dash.seek(curr_time)
|
|
148
|
+
dash.play()
|
|
149
|
+
this.startChangeQuality = false
|
|
150
|
+
}, 100)
|
|
142
151
|
}
|
|
143
152
|
}
|
|
144
153
|
|
|
145
154
|
get _startTime() {
|
|
146
|
-
if (
|
|
147
|
-
|
|
155
|
+
if (
|
|
156
|
+
this._playbackType === Playback.LIVE &&
|
|
157
|
+
this._playlistType !== 'EVENT'
|
|
158
|
+
) {
|
|
159
|
+
return this._extrapolatedStartTime
|
|
148
160
|
}
|
|
149
161
|
|
|
150
|
-
return this._playableRegionStartTime
|
|
162
|
+
return this._playableRegionStartTime
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
get _now() {
|
|
154
|
-
return now()
|
|
166
|
+
return now()
|
|
155
167
|
}
|
|
156
168
|
|
|
157
169
|
// the time in the video element which should represent the start of the sliding window
|
|
158
170
|
// extrapolated to increase in real time (instead of jumping as the early segments are removed)
|
|
159
171
|
get _extrapolatedStartTime() {
|
|
160
172
|
if (!this._localStartTimeCorrelation) {
|
|
161
|
-
return this._playableRegionStartTime
|
|
173
|
+
return this._playableRegionStartTime
|
|
162
174
|
}
|
|
163
175
|
|
|
164
|
-
const corr = this._localStartTimeCorrelation
|
|
165
|
-
const timePassed = this._now - corr.local
|
|
166
|
-
const extrapolatedWindowStartTime = (corr.remote + timePassed) / 1000
|
|
176
|
+
const corr = this._localStartTimeCorrelation
|
|
177
|
+
const timePassed = this._now - corr.local
|
|
178
|
+
const extrapolatedWindowStartTime = (corr.remote + timePassed) / 1000
|
|
167
179
|
|
|
168
180
|
// cap at the end of the extrapolated window duration
|
|
169
|
-
return Math.min(
|
|
181
|
+
return Math.min(
|
|
182
|
+
extrapolatedWindowStartTime,
|
|
183
|
+
this._playableRegionStartTime + this._extrapolatedWindowDuration,
|
|
184
|
+
)
|
|
170
185
|
}
|
|
171
186
|
|
|
172
187
|
// the time in the video element which should represent the end of the content
|
|
173
188
|
// extrapolated to increase in real time (instead of jumping as segments are added)
|
|
174
189
|
get _extrapolatedEndTime() {
|
|
175
|
-
const actualEndTime =
|
|
190
|
+
const actualEndTime =
|
|
191
|
+
this._playableRegionStartTime + this._playableRegionDuration
|
|
176
192
|
|
|
177
193
|
if (!this._localEndTimeCorrelation) {
|
|
178
|
-
return actualEndTime
|
|
194
|
+
return actualEndTime
|
|
179
195
|
}
|
|
180
196
|
|
|
181
|
-
const corr = this._localEndTimeCorrelation
|
|
182
|
-
const timePassed = this._now - corr.local
|
|
183
|
-
const extrapolatedEndTime = (corr.remote + timePassed) / 1000
|
|
197
|
+
const corr = this._localEndTimeCorrelation
|
|
198
|
+
const timePassed = this._now - corr.local
|
|
199
|
+
const extrapolatedEndTime = (corr.remote + timePassed) / 1000
|
|
184
200
|
|
|
185
|
-
return Math.max(
|
|
201
|
+
return Math.max(
|
|
202
|
+
actualEndTime - this._extrapolatedWindowDuration,
|
|
203
|
+
Math.min(extrapolatedEndTime, actualEndTime),
|
|
204
|
+
)
|
|
186
205
|
}
|
|
187
206
|
|
|
188
207
|
get _duration() {
|
|
189
208
|
if (!this._dash) {
|
|
190
|
-
return Infinity
|
|
209
|
+
return Infinity
|
|
191
210
|
}
|
|
192
|
-
return this._dash.duration() ?? Infinity
|
|
211
|
+
return this._dash.duration() ?? Infinity
|
|
193
212
|
}
|
|
194
213
|
|
|
195
214
|
constructor(options: any, i18n: string, playerError?: any) {
|
|
196
|
-
super(options, i18n, playerError)
|
|
215
|
+
super(options, i18n, playerError)
|
|
197
216
|
// backwards compatibility (TODO: remove on 0.3.0)
|
|
198
217
|
// this.options.playback || (this.options.playback = this.options);
|
|
199
218
|
// The size of the start time extrapolation window measured as a multiple of segments.
|
|
200
219
|
// Should be 2 or higher, or 0 to disable. Should only need to be increased above 2 if more than one segment is
|
|
201
220
|
// removed from the start of the playlist at a time. E.g if the playlist is cached for 10 seconds and new chunks are
|
|
202
221
|
// added/removed every 5.
|
|
203
|
-
this._extrapolatedWindowNumSegments =
|
|
222
|
+
this._extrapolatedWindowNumSegments =
|
|
223
|
+
this.options.playback?.extrapolatedWindowNumSegments ?? 2
|
|
204
224
|
|
|
205
225
|
if (this.options.playbackType) {
|
|
206
|
-
this._playbackType = this.options.playbackType
|
|
226
|
+
this._playbackType = this.options.playbackType
|
|
207
227
|
}
|
|
208
228
|
// this._lastTimeUpdate = { current: 0, total: 0 };
|
|
209
229
|
// this._lastDuration = null;
|
|
@@ -237,70 +257,85 @@ export default class DashPlayback extends HTML5Video {
|
|
|
237
257
|
// #EXT-X-PLAYLIST-TYPE
|
|
238
258
|
// this._playlistType = null;
|
|
239
259
|
if (this.options.hlsRecoverAttempts) {
|
|
240
|
-
this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts
|
|
260
|
+
this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts
|
|
241
261
|
}
|
|
242
262
|
}
|
|
243
263
|
|
|
244
264
|
_setup() {
|
|
245
|
-
const dash = DASHJS.MediaPlayer().create()
|
|
246
|
-
this._dash = dash
|
|
247
|
-
this._dash.initialize()
|
|
265
|
+
const dash = DASHJS.MediaPlayer().create()
|
|
266
|
+
this._dash = dash
|
|
267
|
+
this._dash.initialize()
|
|
248
268
|
|
|
249
|
-
const cfg = this.options.dash ?? {}
|
|
269
|
+
const cfg = this.options.dash ?? {}
|
|
250
270
|
|
|
251
|
-
cfg.streaming = cfg.streaming || {}
|
|
252
|
-
cfg.streaming.text = cfg.streaming.text || { defaultEnabled: false }
|
|
271
|
+
cfg.streaming = cfg.streaming || {}
|
|
272
|
+
cfg.streaming.text = cfg.streaming.text || { defaultEnabled: false }
|
|
253
273
|
|
|
254
|
-
this.options.dash && this._dash.updateSettings(cfg)
|
|
274
|
+
this.options.dash && this._dash.updateSettings(cfg)
|
|
255
275
|
|
|
256
|
-
this._dash.attachView(this.el)
|
|
276
|
+
this._dash.attachView(this.el)
|
|
257
277
|
|
|
258
|
-
this._dash.setAutoPlay(false)
|
|
259
|
-
this._dash.attachSource(this.options.src)
|
|
278
|
+
this._dash.setAutoPlay(false)
|
|
279
|
+
this._dash.attachSource(this.options.src)
|
|
260
280
|
|
|
261
|
-
this._dash.on(DASHJS.MediaPlayer.events.ERROR, this._onDASHJSSError)
|
|
262
|
-
this._dash.on(
|
|
281
|
+
this._dash.on(DASHJS.MediaPlayer.events.ERROR, this._onDASHJSSError)
|
|
282
|
+
this._dash.on(
|
|
283
|
+
DASHJS.MediaPlayer.events.PLAYBACK_ERROR,
|
|
284
|
+
this._onPlaybackError,
|
|
285
|
+
)
|
|
263
286
|
|
|
264
287
|
this._dash.on(DASHJS.MediaPlayer.events.STREAM_INITIALIZED, () => {
|
|
265
|
-
const bitrates = dash.getBitrateInfoListFor('video')
|
|
288
|
+
const bitrates = dash.getBitrateInfoListFor('video')
|
|
266
289
|
|
|
267
|
-
this._updatePlaybackType()
|
|
268
|
-
this._fillLevels(bitrates)
|
|
290
|
+
this._updatePlaybackType()
|
|
291
|
+
this._fillLevels(bitrates)
|
|
269
292
|
dash.on(DASHJS.MediaPlayer.events.QUALITY_CHANGE_REQUESTED, (evt) => {
|
|
270
293
|
// TODO
|
|
271
|
-
assert.ok(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
294
|
+
assert.ok(
|
|
295
|
+
this._levels,
|
|
296
|
+
'An array of levels is required to change quality',
|
|
297
|
+
)
|
|
298
|
+
const newLevel = this._levels.find(
|
|
299
|
+
(level) => level.id === evt.newQuality,
|
|
300
|
+
) // TODO or simply this._levels[evt.newQuality]?
|
|
301
|
+
assert.ok(newLevel, 'A valid level is required to change quality')
|
|
302
|
+
this.onLevelSwitch(newLevel.level)
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
this._dash.on(
|
|
307
|
+
DASHJS.MediaPlayer.events.METRIC_ADDED,
|
|
308
|
+
(e: DashMetricEvent) => {
|
|
309
|
+
// Listen for the first manifest request in order to update player UI
|
|
310
|
+
if ((e.metric as string) === 'DVRInfo') {
|
|
311
|
+
// TODO fix typings
|
|
312
|
+
assert.ok(
|
|
313
|
+
this._dash,
|
|
314
|
+
'An instance of dashjs MediaPlayer is required to get metrics',
|
|
315
|
+
)
|
|
316
|
+
const dvrInfo = this._dash.getDashMetrics().getCurrentDVRInfo('video')
|
|
317
|
+
if (dvrInfo) {
|
|
318
|
+
// Extract time info
|
|
319
|
+
this.manifestInfo = dvrInfo.manifestInfo
|
|
320
|
+
}
|
|
286
321
|
}
|
|
287
|
-
}
|
|
288
|
-
|
|
322
|
+
},
|
|
323
|
+
)
|
|
289
324
|
|
|
290
325
|
this._dash.on(DASHJS.MediaPlayer.events.PLAYBACK_RATE_CHANGED, () => {
|
|
291
|
-
this.trigger('dash:playback-rate-changed')
|
|
292
|
-
})
|
|
326
|
+
this.trigger('dash:playback-rate-changed')
|
|
327
|
+
})
|
|
293
328
|
}
|
|
294
329
|
|
|
295
330
|
render() {
|
|
296
|
-
this._ready()
|
|
331
|
+
this._ready()
|
|
297
332
|
|
|
298
|
-
return super.render()
|
|
333
|
+
return super.render()
|
|
299
334
|
}
|
|
300
335
|
|
|
301
336
|
_ready() {
|
|
302
|
-
this._isReadyState = true
|
|
303
|
-
this.trigger(Events.PLAYBACK_READY, this.name)
|
|
337
|
+
this._isReadyState = true
|
|
338
|
+
this.trigger(Events.PLAYBACK_READY, this.name)
|
|
304
339
|
}
|
|
305
340
|
|
|
306
341
|
// TODO
|
|
@@ -333,89 +368,100 @@ export default class DashPlayback extends HTML5Video {
|
|
|
333
368
|
}
|
|
334
369
|
|
|
335
370
|
_startTimeUpdateTimer() {
|
|
336
|
-
this._stopTimeUpdateTimer()
|
|
371
|
+
this._stopTimeUpdateTimer()
|
|
337
372
|
this._timeUpdateTimer = setInterval(() => {
|
|
338
|
-
this._onDurationChange()
|
|
339
|
-
this._onTimeUpdate()
|
|
340
|
-
}, 100)
|
|
373
|
+
this._onDurationChange()
|
|
374
|
+
this._onTimeUpdate()
|
|
375
|
+
}, 100)
|
|
341
376
|
}
|
|
342
377
|
|
|
343
378
|
_stopTimeUpdateTimer() {
|
|
344
379
|
if (this._timeUpdateTimer) {
|
|
345
|
-
clearInterval(this._timeUpdateTimer)
|
|
380
|
+
clearInterval(this._timeUpdateTimer)
|
|
346
381
|
}
|
|
347
382
|
}
|
|
348
383
|
|
|
349
384
|
getProgramDateTime() {
|
|
350
|
-
return this._programDateTime
|
|
385
|
+
return this._programDateTime
|
|
351
386
|
}
|
|
352
387
|
|
|
353
388
|
// the duration on the video element itself should not be used
|
|
354
389
|
// as this does not necesarily represent the duration of the stream
|
|
355
390
|
// https://github.com/clappr/clappr/issues/668#issuecomment-157036678
|
|
356
391
|
getDuration(): Duration {
|
|
357
|
-
assert.ok(
|
|
358
|
-
|
|
392
|
+
assert.ok(
|
|
393
|
+
this._duration !== null,
|
|
394
|
+
'A valid duration is required to get the duration',
|
|
395
|
+
)
|
|
396
|
+
return this._duration
|
|
359
397
|
}
|
|
360
398
|
|
|
361
399
|
getCurrentTime(): TimeValue {
|
|
362
400
|
// e.g. can be < 0 if user pauses near the start
|
|
363
401
|
// eventually they will then be kicked to the end by hlsjs if they run out of buffer
|
|
364
402
|
// before the official start time
|
|
365
|
-
return this._dash ? this._dash.time() : 0
|
|
403
|
+
return this._dash ? this._dash.time() : 0
|
|
366
404
|
}
|
|
367
405
|
|
|
368
406
|
// the time that "0" now represents relative to when playback started
|
|
369
407
|
// for a stream with a sliding window this will increase as content is
|
|
370
408
|
// removed from the beginning
|
|
371
409
|
getStartTimeOffset(): TimeValue {
|
|
372
|
-
return this._startTime
|
|
410
|
+
return this._startTime
|
|
373
411
|
}
|
|
374
412
|
|
|
375
413
|
seekPercentage(percentage: number) {
|
|
376
|
-
let seekTo = this._duration
|
|
414
|
+
let seekTo = this._duration
|
|
377
415
|
|
|
378
416
|
if (percentage > 0) {
|
|
379
|
-
assert.ok(
|
|
380
|
-
|
|
417
|
+
assert.ok(
|
|
418
|
+
this._duration !== null,
|
|
419
|
+
'A valid duration is required to seek by percentage',
|
|
420
|
+
)
|
|
421
|
+
seekTo = this._duration * (percentage / 100)
|
|
381
422
|
}
|
|
382
423
|
|
|
383
|
-
assert.ok(seekTo !== null, 'A valid seek time is required')
|
|
384
|
-
this.seek(seekTo)
|
|
424
|
+
assert.ok(seekTo !== null, 'A valid seek time is required')
|
|
425
|
+
this.seek(seekTo)
|
|
385
426
|
}
|
|
386
427
|
|
|
387
428
|
seek(time: TimeValue) {
|
|
388
429
|
if (time < 0) {
|
|
389
430
|
// eslint-disable-next-line max-len
|
|
390
|
-
Log.warn(
|
|
391
|
-
|
|
431
|
+
Log.warn(
|
|
432
|
+
'Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.',
|
|
433
|
+
)
|
|
434
|
+
time = this.getDuration()
|
|
392
435
|
}
|
|
393
|
-
this.dvrEnabled && this._updateDvr(time < this.getDuration() - 10)
|
|
394
|
-
assert.ok(
|
|
395
|
-
|
|
436
|
+
this.dvrEnabled && this._updateDvr(time < this.getDuration() - 10)
|
|
437
|
+
assert.ok(
|
|
438
|
+
this._dash,
|
|
439
|
+
'An instance of dashjs MediaPlayer is required to seek',
|
|
440
|
+
)
|
|
441
|
+
this._dash.seek(time)
|
|
396
442
|
}
|
|
397
443
|
|
|
398
444
|
seekToLivePoint() {
|
|
399
|
-
this.seek(this.getDuration())
|
|
445
|
+
this.seek(this.getDuration())
|
|
400
446
|
}
|
|
401
447
|
|
|
402
448
|
_updateDvr(status: boolean) {
|
|
403
|
-
this.trigger(Events.PLAYBACK_DVR, status)
|
|
404
|
-
this.trigger(Events.PLAYBACK_STATS_ADD, {
|
|
449
|
+
this.trigger(Events.PLAYBACK_DVR, status)
|
|
450
|
+
this.trigger(Events.PLAYBACK_STATS_ADD, { dvr: status })
|
|
405
451
|
}
|
|
406
452
|
|
|
407
453
|
_updateSettings() {
|
|
408
454
|
if (this._playbackType === Playback.VOD) {
|
|
409
|
-
this.settings.left =
|
|
455
|
+
this.settings.left = ['playpause', 'position', 'duration']
|
|
410
456
|
// this.settings.left.push('playstop');
|
|
411
457
|
} else if (this.dvrEnabled) {
|
|
412
|
-
this.settings.left = ['playpause']
|
|
458
|
+
this.settings.left = ['playpause']
|
|
413
459
|
} else {
|
|
414
|
-
this.settings.left = ['playstop']
|
|
460
|
+
this.settings.left = ['playstop']
|
|
415
461
|
}
|
|
416
462
|
|
|
417
|
-
this.settings.seekEnabled = this.isSeekEnabled()
|
|
418
|
-
this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
|
|
463
|
+
this.settings.seekEnabled = this.isSeekEnabled()
|
|
464
|
+
this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
|
|
419
465
|
}
|
|
420
466
|
|
|
421
467
|
_onPlaybackError = (event: DashPlaybackErrorEvent) => {
|
|
@@ -426,34 +472,37 @@ export default class DashPlayback extends HTML5Video {
|
|
|
426
472
|
// TODO
|
|
427
473
|
// only report/handle errors if they are fatal
|
|
428
474
|
// hlsjs should automatically handle non fatal errors
|
|
429
|
-
this._stopTimeUpdateTimer()
|
|
475
|
+
this._stopTimeUpdateTimer()
|
|
430
476
|
if (event.error === 'capability' && event.event === 'mediasource') {
|
|
431
477
|
// No support for MSE
|
|
432
|
-
const formattedError = this.createError(event.error)
|
|
433
|
-
|
|
434
|
-
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
435
|
-
Log.error(
|
|
436
|
-
'
|
|
437
|
-
|
|
478
|
+
const formattedError = this.createError(event.error)
|
|
479
|
+
|
|
480
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
481
|
+
Log.error(
|
|
482
|
+
'The media cannot be played because it requires a feature ' +
|
|
483
|
+
'that your browser does not support.',
|
|
484
|
+
)
|
|
485
|
+
} else if (
|
|
486
|
+
event.error === 'manifestError' &&
|
|
438
487
|
// Manifest type not supported
|
|
439
|
-
(event.event.id === 'createParser'
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
)
|
|
488
|
+
(event.event.id === 'createParser' ||
|
|
489
|
+
// Codec(s) not supported
|
|
490
|
+
event.event.id === 'codec' ||
|
|
491
|
+
// No streams available to stream
|
|
492
|
+
event.event.id === 'nostreams' ||
|
|
493
|
+
// Error creating Stream object
|
|
494
|
+
event.event.id === 'nostreamscomposed' ||
|
|
495
|
+
// syntax error parsing the manifest
|
|
496
|
+
event.event.id === 'parse' ||
|
|
497
|
+
// a stream has multiplexed audio+video
|
|
498
|
+
event.event.id === 'multiplexedrep')
|
|
499
|
+
) {
|
|
451
500
|
// These errors have useful error messages, so we forward it on
|
|
452
|
-
const formattedError = this.createError(event.error)
|
|
501
|
+
const formattedError = this.createError(event.error)
|
|
453
502
|
|
|
454
|
-
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
503
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
455
504
|
if (event.error) {
|
|
456
|
-
Log.error(event.event.message)
|
|
505
|
+
Log.error(event.event.message)
|
|
457
506
|
}
|
|
458
507
|
} else if (event.error === 'mediasource') {
|
|
459
508
|
// This error happens when dash.js fails to allocate a SourceBuffer
|
|
@@ -462,167 +511,193 @@ export default class DashPlayback extends HTML5Video {
|
|
|
462
511
|
// (audio/video/text) failed allocation.
|
|
463
512
|
// If it's a `MediaError`, dash.js inspects the error object for
|
|
464
513
|
// additional information to append to the error type.
|
|
465
|
-
const formattedError = this.createError(event.error)
|
|
466
|
-
|
|
467
|
-
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
468
|
-
Log.error(event.event)
|
|
469
|
-
} else if (
|
|
514
|
+
const formattedError = this.createError(event.error)
|
|
515
|
+
|
|
516
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
517
|
+
Log.error(event.event)
|
|
518
|
+
} else if (
|
|
519
|
+
event.error === 'capability' &&
|
|
520
|
+
event.event === 'encryptedmedia'
|
|
521
|
+
) {
|
|
470
522
|
// Browser doesn't support EME
|
|
471
523
|
|
|
472
|
-
const formattedError = this.createError(event.error)
|
|
524
|
+
const formattedError = this.createError(event.error)
|
|
473
525
|
|
|
474
|
-
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
475
|
-
Log.error(
|
|
476
|
-
'
|
|
526
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
527
|
+
Log.error(
|
|
528
|
+
'The media cannot be played because it requires encryption ' +
|
|
529
|
+
'that your browser does not support.',
|
|
530
|
+
)
|
|
477
531
|
} else if (event.error === 'key_session') {
|
|
478
532
|
// This block handles pretty much all errors thrown by the
|
|
479
533
|
// encryption subsystem
|
|
480
|
-
const formattedError = this.createError(event.error)
|
|
534
|
+
const formattedError = this.createError(event.error)
|
|
481
535
|
|
|
482
|
-
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
483
|
-
Log.error(event.event)
|
|
536
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
537
|
+
Log.error(event.event)
|
|
484
538
|
} else if (event.error === 'download') {
|
|
485
|
-
const formattedError = this.createError(event.error)
|
|
486
|
-
|
|
487
|
-
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
488
|
-
Log.error(
|
|
489
|
-
'
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
539
|
+
const formattedError = this.createError(event.error)
|
|
540
|
+
|
|
541
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
542
|
+
Log.error(
|
|
543
|
+
'The media playback was aborted because too many consecutive ' +
|
|
544
|
+
'download errors occurred.',
|
|
545
|
+
)
|
|
546
|
+
// } else if (event.error === 'mssError') {
|
|
547
|
+
// const formattedError = this.createError(event.error);
|
|
548
|
+
|
|
549
|
+
// this.trigger(Events.PLAYBACK_ERROR, formattedError);
|
|
550
|
+
// if (event.error) {
|
|
551
|
+
// Log.error(event.error.message);
|
|
552
|
+
// }
|
|
497
553
|
} else {
|
|
498
554
|
// ignore the error
|
|
499
|
-
if (typeof event.error ===
|
|
500
|
-
const formattedError = this.createError(event.error)
|
|
555
|
+
if (typeof event.error === 'object') {
|
|
556
|
+
const formattedError = this.createError(event.error)
|
|
501
557
|
|
|
502
|
-
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
503
|
-
Log.error(event.error.message)
|
|
558
|
+
this.trigger(Events.PLAYBACK_ERROR, formattedError)
|
|
559
|
+
Log.error(event.error.message)
|
|
504
560
|
} else {
|
|
505
|
-
Log.error(event.error)
|
|
561
|
+
Log.error(event.error)
|
|
506
562
|
}
|
|
507
|
-
return
|
|
563
|
+
return
|
|
508
564
|
}
|
|
509
565
|
|
|
510
566
|
// only reset the dash player in 10ms async, so that the rest of the
|
|
511
567
|
// calling function finishes
|
|
512
568
|
setTimeout(() => {
|
|
513
|
-
assert.ok(
|
|
514
|
-
|
|
515
|
-
|
|
569
|
+
assert.ok(
|
|
570
|
+
this._dash,
|
|
571
|
+
'An instance of dashjs MediaPlayer is required to reset',
|
|
572
|
+
)
|
|
573
|
+
this._dash.reset()
|
|
574
|
+
}, 10)
|
|
516
575
|
}
|
|
517
576
|
|
|
518
577
|
_onTimeUpdate() {
|
|
519
578
|
if (this.startChangeQuality) {
|
|
520
|
-
return
|
|
579
|
+
return
|
|
521
580
|
}
|
|
522
581
|
const update = {
|
|
523
582
|
current: this.getCurrentTime(),
|
|
524
583
|
total: this.getDuration(),
|
|
525
|
-
firstFragDateTime: this.getProgramDateTime()
|
|
526
|
-
}
|
|
527
|
-
const isSame =
|
|
584
|
+
firstFragDateTime: this.getProgramDateTime(),
|
|
585
|
+
}
|
|
586
|
+
const isSame =
|
|
587
|
+
this._lastTimeUpdate &&
|
|
528
588
|
update.current === this._lastTimeUpdate.current &&
|
|
529
|
-
update.total === this._lastTimeUpdate.total
|
|
589
|
+
update.total === this._lastTimeUpdate.total
|
|
530
590
|
|
|
531
591
|
if (isSame) {
|
|
532
|
-
return
|
|
592
|
+
return
|
|
533
593
|
}
|
|
534
|
-
this._lastTimeUpdate = update
|
|
535
|
-
this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)
|
|
594
|
+
this._lastTimeUpdate = update
|
|
595
|
+
this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)
|
|
536
596
|
}
|
|
537
597
|
|
|
538
598
|
_onDurationChange() {
|
|
539
|
-
const duration = this.getDuration()
|
|
599
|
+
const duration = this.getDuration()
|
|
540
600
|
|
|
541
601
|
if (this._lastDuration === duration) {
|
|
542
|
-
return
|
|
602
|
+
return
|
|
543
603
|
}
|
|
544
604
|
|
|
545
|
-
this._lastDuration = duration
|
|
546
|
-
super._onDurationChange()
|
|
605
|
+
this._lastDuration = duration
|
|
606
|
+
super._onDurationChange()
|
|
547
607
|
}
|
|
548
608
|
|
|
549
609
|
get dvrEnabled() {
|
|
550
|
-
assert.ok(
|
|
551
|
-
|
|
610
|
+
assert.ok(
|
|
611
|
+
this._dash,
|
|
612
|
+
'An instance of dashjs MediaPlayer is required to get the DVR status',
|
|
613
|
+
)
|
|
614
|
+
return (
|
|
615
|
+
this._dash?.getDVRWindowSize() >= this._minDvrSize &&
|
|
616
|
+
this.getPlaybackType() === Playback.LIVE
|
|
617
|
+
)
|
|
552
618
|
}
|
|
553
619
|
|
|
554
620
|
_onProgress() {
|
|
555
621
|
if (!this._dash) {
|
|
556
|
-
return
|
|
622
|
+
return
|
|
557
623
|
}
|
|
558
624
|
|
|
559
|
-
let buffer = this._dash.getDashMetrics().getCurrentBufferLevel('video')
|
|
625
|
+
let buffer = this._dash.getDashMetrics().getCurrentBufferLevel('video')
|
|
560
626
|
|
|
561
627
|
if (!buffer) {
|
|
562
|
-
buffer = this._dash.getDashMetrics().getCurrentBufferLevel('audio')
|
|
628
|
+
buffer = this._dash.getDashMetrics().getCurrentBufferLevel('audio')
|
|
563
629
|
}
|
|
564
630
|
const progress = {
|
|
565
631
|
start: this.getCurrentTime(),
|
|
566
632
|
current: this.getCurrentTime() + buffer,
|
|
567
|
-
total: this.getDuration()
|
|
568
|
-
}
|
|
633
|
+
total: this.getDuration(),
|
|
634
|
+
}
|
|
569
635
|
|
|
570
|
-
this.trigger(Events.PLAYBACK_PROGRESS, progress, {})
|
|
636
|
+
this.trigger(Events.PLAYBACK_PROGRESS, progress, {})
|
|
571
637
|
}
|
|
572
638
|
|
|
573
639
|
play() {
|
|
574
|
-
trace(`${T} play`, { dash: !!this._dash })
|
|
640
|
+
trace(`${T} play`, { dash: !!this._dash })
|
|
575
641
|
if (!this._dash) {
|
|
576
|
-
this._setup()
|
|
642
|
+
this._setup()
|
|
577
643
|
}
|
|
578
644
|
|
|
579
|
-
super.play()
|
|
580
|
-
this._startTimeUpdateTimer()
|
|
645
|
+
super.play()
|
|
646
|
+
this._startTimeUpdateTimer()
|
|
581
647
|
}
|
|
582
648
|
|
|
583
649
|
pause() {
|
|
584
650
|
if (!this._dash) {
|
|
585
|
-
return
|
|
651
|
+
return
|
|
586
652
|
}
|
|
587
653
|
|
|
588
|
-
super.pause()
|
|
654
|
+
super.pause()
|
|
589
655
|
if (this.dvrEnabled) {
|
|
590
|
-
this._updateDvr(true)
|
|
656
|
+
this._updateDvr(true)
|
|
591
657
|
}
|
|
592
658
|
}
|
|
593
659
|
|
|
594
660
|
stop() {
|
|
595
661
|
if (this._dash) {
|
|
596
|
-
this._stopTimeUpdateTimer()
|
|
597
|
-
this._dash.reset()
|
|
598
|
-
super.stop()
|
|
599
|
-
this._dash = null
|
|
662
|
+
this._stopTimeUpdateTimer()
|
|
663
|
+
this._dash.reset()
|
|
664
|
+
super.stop()
|
|
665
|
+
this._dash = null
|
|
600
666
|
}
|
|
601
667
|
}
|
|
602
668
|
|
|
603
669
|
destroy() {
|
|
604
|
-
this._stopTimeUpdateTimer()
|
|
670
|
+
this._stopTimeUpdateTimer()
|
|
605
671
|
if (this._dash) {
|
|
606
|
-
this._dash.off(DASHJS.MediaPlayer.events.ERROR, this._onDASHJSSError)
|
|
607
|
-
this._dash.off(
|
|
608
|
-
|
|
609
|
-
|
|
672
|
+
this._dash.off(DASHJS.MediaPlayer.events.ERROR, this._onDASHJSSError)
|
|
673
|
+
this._dash.off(
|
|
674
|
+
DASHJS.MediaPlayer.events.PLAYBACK_ERROR,
|
|
675
|
+
this._onPlaybackError,
|
|
676
|
+
)
|
|
677
|
+
this._dash.off(
|
|
678
|
+
DASHJS.MediaPlayer.events.MANIFEST_LOADED,
|
|
679
|
+
this.getDuration,
|
|
680
|
+
)
|
|
681
|
+
this._dash.reset()
|
|
610
682
|
}
|
|
611
|
-
this._dash = null
|
|
612
|
-
return super.destroy()
|
|
683
|
+
this._dash = null
|
|
684
|
+
return super.destroy()
|
|
613
685
|
}
|
|
614
686
|
|
|
615
687
|
_updatePlaybackType() {
|
|
616
|
-
assert.ok(
|
|
617
|
-
|
|
688
|
+
assert.ok(
|
|
689
|
+
this._dash,
|
|
690
|
+
'An instance of dashjs MediaPlayer is required to update the playback type',
|
|
691
|
+
)
|
|
692
|
+
this._playbackType = this._dash.isDynamic() ? Playback.LIVE : Playback.VOD
|
|
618
693
|
}
|
|
619
694
|
|
|
620
695
|
_fillLevels(levels: BitrateInfo[]) {
|
|
621
696
|
// TOOD check that levels[i].qualityIndex === i
|
|
622
697
|
this._levels = levels.map((level) => {
|
|
623
|
-
return { id: level.qualityIndex, level: level }
|
|
624
|
-
})
|
|
625
|
-
this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)
|
|
698
|
+
return { id: level.qualityIndex, level: level }
|
|
699
|
+
})
|
|
700
|
+
this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)
|
|
626
701
|
}
|
|
627
702
|
|
|
628
703
|
// _onLevelUpdated(_: any, data) {
|
|
@@ -771,26 +846,41 @@ export default class DashPlayback extends HTML5Video {
|
|
|
771
846
|
height: currentLevel.height,
|
|
772
847
|
width: currentLevel.width,
|
|
773
848
|
bitrate: currentLevel.bitrate,
|
|
774
|
-
level: currentLevel.qualityIndex
|
|
775
|
-
})
|
|
849
|
+
level: currentLevel.qualityIndex,
|
|
850
|
+
})
|
|
776
851
|
}
|
|
777
852
|
|
|
778
853
|
getPlaybackType() {
|
|
779
|
-
return this._playbackType
|
|
854
|
+
return this._playbackType
|
|
780
855
|
}
|
|
781
856
|
|
|
782
857
|
isSeekEnabled() {
|
|
783
|
-
return
|
|
858
|
+
return this._playbackType === Playback.VOD || this.dvrEnabled
|
|
784
859
|
}
|
|
785
860
|
}
|
|
786
861
|
|
|
787
862
|
DashPlayback.canPlay = function (resource, mimeType) {
|
|
788
|
-
const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
|
|
789
|
-
const isDash =
|
|
790
|
-
|
|
863
|
+
const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
|
|
864
|
+
const isDash =
|
|
865
|
+
(resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'mpd') ||
|
|
866
|
+
mimeType === 'application/dash+xml' ||
|
|
867
|
+
mimeType === 'video/mp4'
|
|
791
868
|
// TODO check
|
|
792
|
-
const
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
869
|
+
const ms = window.MediaSource
|
|
870
|
+
const mms =
|
|
871
|
+
'ManagedMediaSource' in window ? window.ManagedMediaSource : undefined
|
|
872
|
+
const wms =
|
|
873
|
+
'WebKitMediaSource' in window ? window.WebKitMediaSource : undefined
|
|
874
|
+
const ctor = ms || mms || wms
|
|
875
|
+
|
|
876
|
+
const hasSupport = typeof ctor === 'function'
|
|
877
|
+
trace(`${T} canPlay`, {
|
|
878
|
+
hasSupport,
|
|
879
|
+
isDash,
|
|
880
|
+
resource,
|
|
881
|
+
ms: typeof ms === 'function',
|
|
882
|
+
mms: typeof mms === 'function',
|
|
883
|
+
wms: typeof wms === 'function',
|
|
884
|
+
})
|
|
885
|
+
return !!(hasSupport && isDash)
|
|
886
|
+
}
|