@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,706 @@
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 HLSJS from 'hls.js'
7
+
8
+ const { now, listContainsIgnoreCase } = Utils
9
+ const AUTO = -1
10
+ const DEFAULT_RECOVER_ATTEMPTS = 16
11
+
12
+ Events.register('PLAYBACK_FRAGMENT_CHANGED')
13
+ Events.register('PLAYBACK_FRAGMENT_PARSING_METADATA')
14
+
15
+ export default class HlsjsPlayback extends HTML5Video {
16
+ get name() { return 'hls' }
17
+
18
+ get supportedVersion() { return { min: CLAPPR_CORE_VERSION } }
19
+
20
+ get levels() { return this._levels || [] }
21
+
22
+ get currentLevel() {
23
+ if (this._currentLevel === null || this._currentLevel === undefined)
24
+ return AUTO
25
+ else
26
+ return this._currentLevel //0 is a valid level ID
27
+
28
+ }
29
+
30
+ get isReady() {
31
+ return this._isReadyState
32
+ }
33
+
34
+ set currentLevel(id) {
35
+ this._currentLevel = id
36
+ this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)
37
+ if (this.options.playback.hlsUseNextLevel)
38
+ this._hls.nextLevel = this._currentLevel
39
+ else
40
+ this._hls.currentLevel = this._currentLevel
41
+ }
42
+
43
+ get latency() {
44
+ return this._hls.latency
45
+ }
46
+
47
+ get currentProgramDateTime() {
48
+ return this._hls.playingDate
49
+ }
50
+
51
+ get _startTime() {
52
+ if (this._playbackType === Playback.LIVE && this._playlistType !== 'EVENT')
53
+ return this._extrapolatedStartTime
54
+
55
+ return this._playableRegionStartTime
56
+ }
57
+
58
+ get _now() {
59
+ return now()
60
+ }
61
+
62
+ // the time in the video element which should represent the start of the sliding window
63
+ // extrapolated to increase in real time (instead of jumping as the early segments are removed)
64
+ get _extrapolatedStartTime() {
65
+ if (!this._localStartTimeCorrelation)
66
+ return this._playableRegionStartTime
67
+
68
+ let corr = this._localStartTimeCorrelation
69
+ let timePassed = this._now - corr.local
70
+ let extrapolatedWindowStartTime = (corr.remote + timePassed) / 1000
71
+ // cap at the end of the extrapolated window duration
72
+ return Math.min(extrapolatedWindowStartTime, this._playableRegionStartTime + this._extrapolatedWindowDuration)
73
+ }
74
+
75
+ // the time in the video element which should represent the end of the content
76
+ // extrapolated to increase in real time (instead of jumping as segments are added)
77
+ get _extrapolatedEndTime() {
78
+ let actualEndTime = this._playableRegionStartTime + this._playableRegionDuration
79
+ if (!this._localEndTimeCorrelation) return actualEndTime
80
+ const correlation = this._localEndTimeCorrelation
81
+ const timePassed = this._now - correlation.local
82
+ const extrapolatedEndTime = (correlation.remote + timePassed) / 1000
83
+ return Math.max(actualEndTime - this._extrapolatedWindowDuration, Math.min(extrapolatedEndTime, actualEndTime))
84
+ }
85
+
86
+ get _duration() {
87
+ return this._extrapolatedEndTime - this._startTime
88
+ }
89
+
90
+ // Returns the duration (seconds) of the window that the extrapolated start time is allowed
91
+ // to move in before being capped.
92
+ // The extrapolated start time should never reach the cap at the end of the window as the
93
+ // window should slide as chunks are removed from the start.
94
+ // This also applies to the extrapolated end time in the same way.
95
+ //
96
+ // If chunks aren't being removed for some reason that the start time will reach and remain fixed at
97
+ // playableRegionStartTime + extrapolatedWindowDuration
98
+ //
99
+ // <-- window duration -->
100
+ // I.e playableRegionStartTime |-----------------------|
101
+ // | --> . . .
102
+ // . --> | --> . .
103
+ // . . --> | --> .
104
+ // . . . --> |
105
+ // . . . .
106
+ // extrapolatedStartTime
107
+ get _extrapolatedWindowDuration() {
108
+ if (this._segmentTargetDuration === null)
109
+ return 0
110
+
111
+ return this._extrapolatedWindowNumSegments * this._segmentTargetDuration
112
+ }
113
+
114
+ get bandwidthEstimate() {
115
+ return this._hls && this._hls.bandwidthEstimate
116
+ }
117
+
118
+ get defaultOptions() {
119
+ return { preload: true }
120
+ }
121
+
122
+ get customListeners() {
123
+ return this.options.hlsPlayback && this.options.hlsPlayback.customListeners || []
124
+ }
125
+
126
+ get sourceMedia() {
127
+ return this.options.src
128
+ }
129
+
130
+ get currentTimestamp() {
131
+ if (!this._currentFragment) return null
132
+ const startTime = this._currentFragment.programDateTime
133
+ const playbackTime = this.el.currentTime
134
+ const playTimeOffSet = playbackTime - this._currentFragment.start
135
+ const currentTimestampInMs = startTime + playTimeOffSet * 1000
136
+ return currentTimestampInMs / 1000
137
+ }
138
+
139
+ static get HLSJS() {
140
+ return HLSJS
141
+ }
142
+
143
+ constructor(...args) {
144
+ super(...args)
145
+ this.options.hlsPlayback = { ...this.defaultOptions, ...this.options.hlsPlayback }
146
+ this._setInitialState()
147
+ }
148
+
149
+ _setInitialState() {
150
+ this._minDvrSize = typeof (this.options.hlsMinimumDvrSize) === 'undefined' ? 60 : this.options.hlsMinimumDvrSize
151
+ // The size of the start time extrapolation window measured as a multiple of segments.
152
+ // Should be 2 or higher, or 0 to disable. Should only need to be increased above 2 if more than one segment is
153
+ // removed from the start of the playlist at a time. E.g if the playlist is cached for 10 seconds and new chunks are
154
+ // added/removed every 5.
155
+ this._extrapolatedWindowNumSegments = !this.options.playback || typeof (this.options.playback.extrapolatedWindowNumSegments) === 'undefined' ? 2 : this.options.playback.extrapolatedWindowNumSegments
156
+
157
+ this._playbackType = Playback.VOD
158
+ this._lastTimeUpdate = { current: 0, total: 0 }
159
+ this._lastDuration = null
160
+ // for hls streams which have dvr with a sliding window,
161
+ // the content at the start of the playlist is removed as new
162
+ // content is appended at the end.
163
+ // this means the actual playable start time will increase as the
164
+ // start content is deleted
165
+ // For streams with dvr where the entire recording is kept from the
166
+ // beginning this should stay as 0
167
+ this._playableRegionStartTime = 0
168
+ // {local, remote} remote is the time in the video element that should represent 0
169
+ // local is the system time when the 'remote' measurment took place
170
+ this._localStartTimeCorrelation = null
171
+ // {local, remote} remote is the time in the video element that should represents the end
172
+ // local is the system time when the 'remote' measurment took place
173
+ this._localEndTimeCorrelation = null
174
+ // if content is removed from the beginning then this empty area should
175
+ // be ignored. "playableRegionDuration" excludes the empty area
176
+ this._playableRegionDuration = 0
177
+ // #EXT-X-PROGRAM-DATE-TIME
178
+ this._programDateTime = 0
179
+ // true when the actual duration is longer than hlsjs's live sync point
180
+ // when this is false playableRegionDuration will be the actual duration
181
+ // when this is true playableRegionDuration will exclude the time after the sync point
182
+ this._durationExcludesAfterLiveSyncPoint = false
183
+ // #EXT-X-TARGETDURATION
184
+ this._segmentTargetDuration = null
185
+ // #EXT-X-PLAYLIST-TYPE
186
+ this._playlistType = null
187
+ this._recoverAttemptsRemaining = this.options.hlsRecoverAttempts || DEFAULT_RECOVER_ATTEMPTS
188
+ }
189
+
190
+ _setup() {
191
+ this._destroyHLSInstance()
192
+ this._createHLSInstance()
193
+ this._listenHLSEvents()
194
+ this._attachHLSMedia()
195
+ }
196
+
197
+ _destroyHLSInstance() {
198
+ if (!this._hls) return
199
+ this._manifestParsed = false
200
+ this._ccIsSetup = false
201
+ this._ccTracksUpdated = false
202
+ this._setInitialState()
203
+ this._hls.destroy()
204
+ this._hls = null
205
+ }
206
+
207
+ _createHLSInstance() {
208
+ const config = { ...this.options.playback.hlsjsConfig }
209
+ this._hls = new HLSJS(config)
210
+ }
211
+
212
+ _attachHLSMedia() {
213
+ if (!this._hls) return
214
+ this._hls.attachMedia(this.el)
215
+ }
216
+
217
+ _listenHLSEvents() {
218
+ if (!this._hls) return
219
+ this._hls.once(HLSJS.Events.MEDIA_ATTACHED, () => { this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src) })
220
+ this._hls.on(HLSJS.Events.MANIFEST_PARSED, () => this._manifestParsed = true)
221
+ this._hls.on(HLSJS.Events.LEVEL_LOADED, (evt, data) => this._updatePlaybackType(evt, data))
222
+ this._hls.on(HLSJS.Events.LEVEL_UPDATED, (evt, data) => this._onLevelUpdated(evt, data))
223
+ this._hls.on(HLSJS.Events.LEVEL_SWITCHING, (evt,data) => this._onLevelSwitch(evt, data))
224
+ this._hls.on(HLSJS.Events.FRAG_CHANGED, (evt, data) => this._onFragmentChanged(evt, data))
225
+ this._hls.on(HLSJS.Events.FRAG_LOADED, (evt, data) => this._onFragmentLoaded(evt, data))
226
+ this._hls.on(HLSJS.Events.FRAG_PARSING_METADATA, (evt, data) => this._onFragmentParsingMetadata(evt, data))
227
+ this._hls.on(HLSJS.Events.ERROR, (evt, data) => this._onHLSJSError(evt, data))
228
+ this._hls.on(HLSJS.Events.SUBTITLE_TRACK_LOADED, (evt, data) => this._onSubtitleLoaded(evt, data))
229
+ this._hls.on(HLSJS.Events.SUBTITLE_TRACKS_UPDATED, () => this._ccTracksUpdated = true)
230
+ this.bindCustomListeners()
231
+ }
232
+
233
+ bindCustomListeners() {
234
+ this.customListeners.forEach(item => {
235
+ const requestedEventName = item.eventName
236
+ const typeOfListener = item.once ? 'once': 'on'
237
+ requestedEventName && this._hls[`${typeOfListener}`](requestedEventName, item.callback)
238
+ })
239
+ }
240
+
241
+ unbindCustomListeners() {
242
+ this.customListeners.forEach(item => {
243
+ const requestedEventName = item.eventName
244
+ requestedEventName && this._hls.off(requestedEventName, item.callback)
245
+ })
246
+ }
247
+
248
+ _onFragmentParsingMetadata(evt, data) {
249
+ this.trigger(Events.Custom.PLAYBACK_FRAGMENT_PARSING_METADATA, { evt, data })
250
+ }
251
+
252
+ render() {
253
+ this._ready()
254
+ return super.render()
255
+ }
256
+
257
+ _ready() {
258
+ if (this._isReadyState) return
259
+ !this._hls && this._setup()
260
+ this._isReadyState = true
261
+ this.trigger(Events.PLAYBACK_READY, this.name)
262
+ }
263
+
264
+ _recover(evt, data, error) {
265
+ if (!this._recoveredDecodingError) {
266
+ this._recoveredDecodingError = true
267
+ this._hls.recoverMediaError()
268
+ } else if (!this._recoveredAudioCodecError) {
269
+ this._recoveredAudioCodecError = true
270
+ this._hls.swapAudioCodec()
271
+ this._hls.recoverMediaError()
272
+ } else {
273
+ Log.error('hlsjs: failed to recover', { evt, data })
274
+ error.level = PlayerError.Levels.FATAL
275
+ const formattedError = this.createError(error)
276
+ this.trigger(Events.PLAYBACK_ERROR, formattedError)
277
+ this.stop()
278
+ }
279
+ }
280
+
281
+ // override
282
+ // this playback manages the src on the video element itself
283
+ _setupSrc(srcUrl) {} // eslint-disable-line no-unused-vars
284
+
285
+ _startTimeUpdateTimer() {
286
+ if (this._timeUpdateTimer) return
287
+ this._timeUpdateTimer = setInterval(() => {
288
+ this._onDurationChange()
289
+ this._onTimeUpdate()
290
+ }, 100)
291
+ }
292
+
293
+ _stopTimeUpdateTimer() {
294
+ if (!this._timeUpdateTimer) return
295
+ clearInterval(this._timeUpdateTimer)
296
+ this._timeUpdateTimer = null
297
+ }
298
+
299
+ getProgramDateTime() {
300
+ return this._programDateTime
301
+ }
302
+
303
+ // the duration on the video element itself should not be used
304
+ // as this does not necesarily represent the duration of the stream
305
+ // https://github.com/clappr/clappr/issues/668#issuecomment-157036678
306
+ getDuration() {
307
+ return this._duration
308
+ }
309
+
310
+ getCurrentTime() {
311
+ // e.g. can be < 0 if user pauses near the start
312
+ // eventually they will then be kicked to the end by hlsjs if they run out of buffer
313
+ // before the official start time
314
+ return Math.max(0, this.el.currentTime - this._startTime)
315
+ }
316
+
317
+ // the time that "0" now represents relative to when playback started
318
+ // for a stream with a sliding window this will increase as content is
319
+ // removed from the beginning
320
+ getStartTimeOffset() {
321
+ return this._startTime
322
+ }
323
+
324
+ seekPercentage(percentage) {
325
+ const seekTo = (percentage > 0)
326
+ ? this._duration * (percentage / 100)
327
+ : this._duration
328
+ this.seek(seekTo)
329
+ }
330
+
331
+ seek(time) {
332
+ if (time < 0) {
333
+ Log.warn('Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.')
334
+ time = this.getDuration()
335
+ }
336
+ // assume live if time within 3 seconds of end of stream
337
+ this.dvrEnabled && this._updateDvr(time < this.getDuration()-3)
338
+ time += this._startTime
339
+ this.el.currentTime = time
340
+ }
341
+
342
+ seekToLivePoint() {
343
+ this.seek(this.getDuration())
344
+ }
345
+
346
+ _updateDvr(status) {
347
+ this.trigger(Events.PLAYBACK_DVR, status)
348
+ this.trigger(Events.PLAYBACK_STATS_ADD, { 'dvr': status })
349
+ }
350
+
351
+ _updateSettings() {
352
+ if (this._playbackType === Playback.VOD)
353
+ this.settings.left = ['playpause', 'position', 'duration']
354
+ else if (this.dvrEnabled)
355
+ this.settings.left = ['playpause']
356
+ else
357
+ this.settings.left = ['playstop']
358
+
359
+ this.settings.seekEnabled = this.isSeekEnabled()
360
+ this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
361
+ }
362
+
363
+ _onHLSJSError(evt, data) {
364
+ const error = {
365
+ code: `${data.type}_${data.details}`,
366
+ description: `${this.name} error: type: ${data.type}, details: ${data.details}`,
367
+ raw: data,
368
+ }
369
+ let formattedError
370
+ if (data.response) error.description += `, response: ${JSON.stringify(data.response)}`
371
+ // only report/handle errors if they are fatal
372
+ // hlsjs should automatically handle non fatal errors
373
+ if (data.fatal) {
374
+ if (this._recoverAttemptsRemaining > 0) {
375
+ this._recoverAttemptsRemaining -= 1
376
+ switch (data.type) {
377
+ case HLSJS.ErrorTypes.NETWORK_ERROR:
378
+ switch (data.details) {
379
+ // The following network errors cannot be recovered with HLS.startLoad()
380
+ // For more details, see https://github.com/video-dev/hls.js/blob/master/doc/design.md#error-detection-and-handling
381
+ // For "level load" fatal errors, see https://github.com/video-dev/hls.js/issues/1138
382
+ case HLSJS.ErrorDetails.MANIFEST_LOAD_ERROR:
383
+ case HLSJS.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
384
+ case HLSJS.ErrorDetails.MANIFEST_PARSING_ERROR:
385
+ case HLSJS.ErrorDetails.LEVEL_LOAD_ERROR:
386
+ case HLSJS.ErrorDetails.LEVEL_LOAD_TIMEOUT:
387
+ Log.error('hlsjs: unrecoverable network fatal error.', { evt, data })
388
+ formattedError = this.createError(error)
389
+ this.trigger(Events.PLAYBACK_ERROR, formattedError)
390
+ this.stop()
391
+ break
392
+ default:
393
+ Log.warn('hlsjs: trying to recover from network error.', { evt, data })
394
+ error.level = PlayerError.Levels.WARN
395
+ this._hls.startLoad()
396
+ break
397
+ }
398
+ break
399
+ case HLSJS.ErrorTypes.MEDIA_ERROR:
400
+ Log.warn('hlsjs: trying to recover from media error.', { evt, data })
401
+ error.level = PlayerError.Levels.WARN
402
+ this._recover(evt, data, error)
403
+ break
404
+ default:
405
+ Log.error('hlsjs: could not recover from error.', { evt, data })
406
+ formattedError = this.createError(error)
407
+ this.trigger(Events.PLAYBACK_ERROR, formattedError)
408
+ this.stop()
409
+ break
410
+ }
411
+ } else {
412
+ Log.error('hlsjs: could not recover from error after maximum number of attempts.', { evt, data })
413
+ formattedError = this.createError(error)
414
+ this.trigger(Events.PLAYBACK_ERROR, formattedError)
415
+ this.stop()
416
+ }
417
+ } else {
418
+ // Transforms HLSJS.ErrorDetails.KEY_LOAD_ERROR non-fatal error to
419
+ // playback fatal error if triggerFatalErrorOnResourceDenied playback
420
+ // option is set. HLSJS.ErrorTypes.KEY_SYSTEM_ERROR are fatal errors
421
+ // and therefore already handled.
422
+ if (this.options.playback.triggerFatalErrorOnResourceDenied && this._keyIsDenied(data)) {
423
+ Log.error('hlsjs: could not load decrypt key.', { evt, data })
424
+ formattedError = this.createError(error)
425
+ this.trigger(Events.PLAYBACK_ERROR, formattedError)
426
+ this.stop()
427
+ return
428
+ }
429
+
430
+ error.level = PlayerError.Levels.WARN
431
+ Log.warn('hlsjs: non-fatal error occurred', { evt, data })
432
+ }
433
+ }
434
+
435
+ _keyIsDenied(data) {
436
+ return data.type === HLSJS.ErrorTypes.NETWORK_ERROR
437
+ && data.details === HLSJS.ErrorDetails.KEY_LOAD_ERROR
438
+ && data.response
439
+ && data.response.code >= 400
440
+ }
441
+
442
+ _onTimeUpdate() {
443
+ const update = { current: this.getCurrentTime(), total: this.getDuration(), firstFragDateTime: this.getProgramDateTime() }
444
+ const isSame = this._lastTimeUpdate && (
445
+ update.current === this._lastTimeUpdate.current &&
446
+ update.total === this._lastTimeUpdate.total)
447
+ if (isSame) return
448
+ this._lastTimeUpdate = update
449
+ this.trigger(Events.PLAYBACK_TIMEUPDATE, update, this.name)
450
+ }
451
+
452
+ _onDurationChange() {
453
+ const duration = this.getDuration()
454
+ if (this._lastDuration === duration) return
455
+ this._lastDuration = duration
456
+ super._onDurationChange()
457
+ }
458
+
459
+ _onProgress() {
460
+ if (!this.el.buffered.length) return
461
+ let buffered = []
462
+ let bufferedPos = 0
463
+ for (let i = 0; i < this.el.buffered.length; i++) {
464
+ buffered = [...buffered, {
465
+ // for a stream with sliding window dvr something that is buffered my slide off the start of the timeline
466
+ start: Math.max(0, this.el.buffered.start(i) - this._playableRegionStartTime),
467
+ end: Math.max(0, this.el.buffered.end(i) - this._playableRegionStartTime)
468
+ }]
469
+ if (this.el.currentTime >= buffered[i].start && this.el.currentTime <= buffered[i].end)
470
+ bufferedPos = i
471
+
472
+ }
473
+ const progress = {
474
+ start: buffered[bufferedPos].start,
475
+ current: buffered[bufferedPos].end,
476
+ total: this.getDuration()
477
+ }
478
+ this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)
479
+ }
480
+
481
+ load(url) {
482
+ this._stopTimeUpdateTimer()
483
+ this.options.src = url
484
+ this._setup()
485
+ }
486
+
487
+ play() {
488
+ !this._hls && this._setup()
489
+ !this._manifestParsed && !this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src)
490
+ super.play()
491
+ this._startTimeUpdateTimer()
492
+ }
493
+
494
+ pause() {
495
+ if (!this._hls) return
496
+ this.el.pause()
497
+ if (this.dvrEnabled) this._updateDvr(true)
498
+ }
499
+
500
+ stop() {
501
+ this._stopTimeUpdateTimer()
502
+ if (this._hls) super.stop()
503
+ this._destroyHLSInstance()
504
+ }
505
+
506
+ destroy() {
507
+ this._stopTimeUpdateTimer()
508
+ this._destroyHLSInstance()
509
+ super.destroy()
510
+ }
511
+
512
+ _updatePlaybackType(evt, data) {
513
+ this._playbackType = data.details.live ? Playback.LIVE : Playback.VOD
514
+ this._onLevelUpdated(evt, data)
515
+ // Live stream subtitle tracks detection hack (may not immediately available)
516
+ if (this._ccTracksUpdated && this._playbackType === Playback.LIVE && this.hasClosedCaptionsTracks)
517
+ this._onSubtitleLoaded()
518
+
519
+ }
520
+
521
+ _fillLevels() {
522
+ this._levels = this._hls.levels.map((level, index) => {
523
+ return { id: index, level: level, label: `${level.bitrate/1000}Kbps` }
524
+ })
525
+ this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)
526
+ }
527
+
528
+ _onLevelUpdated(evt, data) {
529
+ this._segmentTargetDuration = data.details.targetduration
530
+ this._playlistType = data.details.type || null
531
+ let startTimeChanged = false
532
+ let durationChanged = false
533
+ let fragments = data.details.fragments
534
+ let previousPlayableRegionStartTime = this._playableRegionStartTime
535
+ let previousPlayableRegionDuration = this._playableRegionDuration
536
+ if (fragments.length === 0) return
537
+ // #EXT-X-PROGRAM-DATE-TIME
538
+ if (fragments[0].rawProgramDateTime)
539
+ this._programDateTime = fragments[0].rawProgramDateTime
540
+ if (this._playableRegionStartTime !== fragments[0].start) {
541
+ startTimeChanged = true
542
+ this._playableRegionStartTime = fragments[0].start
543
+ }
544
+
545
+ if (startTimeChanged) {
546
+ if (!this._localStartTimeCorrelation) {
547
+ // set the correlation to map to middle of the extrapolation window
548
+ this._localStartTimeCorrelation = {
549
+ local: this._now,
550
+ remote: (fragments[0].start + (this._extrapolatedWindowDuration/2)) * 1000
551
+ }
552
+ } else {
553
+ // check if the correlation still works
554
+ let corr = this._localStartTimeCorrelation
555
+ let timePassed = this._now - corr.local
556
+ // this should point to a time within the extrapolation window
557
+ let startTime = (corr.remote + timePassed) / 1000
558
+ if (startTime < fragments[0].start) {
559
+ // our start time is now earlier than the first chunk
560
+ // (maybe the chunk was removed early)
561
+ // reset correlation so that it sits at the beginning of the first available chunk
562
+ this._localStartTimeCorrelation = {
563
+ local: this._now,
564
+ remote: fragments[0].start * 1000
565
+ }
566
+ } else if (startTime > previousPlayableRegionStartTime + this._extrapolatedWindowDuration) {
567
+ // start time was past the end of the old extrapolation window (so would have been capped)
568
+ // see if now that time would be inside the window, and if it would be set the correlation
569
+ // so that it resumes from the time it was at at the end of the old window
570
+ // update the correlation so that the time starts counting again from the value it's on now
571
+ this._localStartTimeCorrelation = {
572
+ local: this._now,
573
+ remote: Math.max(fragments[0].start, previousPlayableRegionStartTime + this._extrapolatedWindowDuration) * 1000
574
+ }
575
+ }
576
+ }
577
+ }
578
+
579
+ let newDuration = data.details.totalduration
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
+ let fragmentTargetDuration = data.details.targetduration
585
+ let hlsjsConfig = this.options.playback.hlsjsConfig || {}
586
+ let liveSyncDurationCount = hlsjsConfig.liveSyncDurationCount || HLSJS.DefaultConfig.liveSyncDurationCount
587
+ let hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount
588
+ if (hiddenAreaDuration <= newDuration) {
589
+ newDuration -= hiddenAreaDuration
590
+ this._durationExcludesAfterLiveSyncPoint = true
591
+ } else { this._durationExcludesAfterLiveSyncPoint = false }
592
+
593
+ }
594
+ if (newDuration !== this._playableRegionDuration) {
595
+ durationChanged = true
596
+ this._playableRegionDuration = newDuration
597
+ }
598
+ // Note the end time is not the playableRegionDuration
599
+ // The end time will always increase even if content is removed from the beginning
600
+ let endTime = fragments[0].start + newDuration
601
+ let previousEndTime = previousPlayableRegionStartTime + previousPlayableRegionDuration
602
+ let endTimeChanged = endTime !== previousEndTime
603
+ if (endTimeChanged) {
604
+ if (!this._localEndTimeCorrelation) {
605
+ // set the correlation to map to the end
606
+ this._localEndTimeCorrelation = {
607
+ local: this._now,
608
+ remote: endTime * 1000
609
+ }
610
+ } else {
611
+ // check if the correlation still works
612
+ let corr = this._localEndTimeCorrelation
613
+ let timePassed = this._now - corr.local
614
+ // this should point to a time within the extrapolation window from the end
615
+ let extrapolatedEndTime = (corr.remote + timePassed) / 1000
616
+ if (extrapolatedEndTime > endTime) {
617
+ this._localEndTimeCorrelation = {
618
+ local: this._now,
619
+ remote: endTime * 1000
620
+ }
621
+ } else if (extrapolatedEndTime < endTime - this._extrapolatedWindowDuration) {
622
+ // our extrapolated end time is now earlier than the extrapolation window from the actual end time
623
+ // (maybe a chunk became available early)
624
+ // reset correlation so that it sits at the beginning of the extrapolation window from the end time
625
+ this._localEndTimeCorrelation = {
626
+ local: this._now,
627
+ remote: (endTime - this._extrapolatedWindowDuration) * 1000
628
+ }
629
+ } else if (extrapolatedEndTime > previousEndTime) {
630
+ // end time was past the old end time (so would have been capped)
631
+ // set the correlation so that it resumes from the time it was at at the end of the old window
632
+ this._localEndTimeCorrelation = {
633
+ local: this._now,
634
+ remote: previousEndTime * 1000
635
+ }
636
+ }
637
+ }
638
+ }
639
+
640
+ // now that the values have been updated call any methods that use on them so they get the updated values
641
+ // immediately
642
+ durationChanged && this._onDurationChange()
643
+ startTimeChanged && this._onProgress()
644
+ }
645
+
646
+ _onFragmentChanged(evt, data) {
647
+ this._currentFragment = data.frag
648
+ this.trigger(Events.Custom.PLAYBACK_FRAGMENT_CHANGED, data)
649
+ }
650
+
651
+ _onFragmentLoaded(evt, data) {
652
+ this.trigger(Events.PLAYBACK_FRAGMENT_LOADED, data)
653
+ }
654
+
655
+ _onSubtitleLoaded() {
656
+ // This event may be triggered multiple times
657
+ // Setup CC only once (disable CC by default)
658
+ if (!this._ccIsSetup) {
659
+ this.trigger(Events.PLAYBACK_SUBTITLE_AVAILABLE)
660
+ const trackId = this._playbackType === Playback.LIVE ? -1 : this.closedCaptionsTrackId
661
+ this.closedCaptionsTrackId = trackId
662
+ this._ccIsSetup = true
663
+ }
664
+ }
665
+
666
+ _onLevelSwitch(evt, data) {
667
+ if (!this.levels.length) this._fillLevels()
668
+ this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
669
+ this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
670
+ let currentLevel = this._hls.levels[data.level]
671
+ if (currentLevel) {
672
+ // TODO should highDefinition be private and maybe have a read only accessor if it's used somewhere
673
+ this.highDefinition = (currentLevel.height >= 720 || (currentLevel.bitrate / 1000) >= 2000)
674
+ this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)
675
+ this.trigger(Events.PLAYBACK_BITRATE, {
676
+ height: currentLevel.height,
677
+ width: currentLevel.width,
678
+ bandwidth: currentLevel.bitrate,
679
+ bitrate: currentLevel.bitrate,
680
+ level: data.level
681
+ })
682
+ }
683
+ }
684
+
685
+ get dvrEnabled() {
686
+ // enabled when:
687
+ // - the duration does not include content after hlsjs's live sync point
688
+ // - the playable region duration is longer than the configured duration to enable dvr after
689
+ // - the playback type is LIVE.
690
+ return (this._durationExcludesAfterLiveSyncPoint && this._duration >= this._minDvrSize && this.getPlaybackType() === Playback.LIVE)
691
+ }
692
+
693
+ getPlaybackType() {
694
+ return this._playbackType
695
+ }
696
+
697
+ isSeekEnabled() {
698
+ return (this._playbackType === Playback.VOD || this.dvrEnabled)
699
+ }
700
+ }
701
+
702
+ HlsjsPlayback.canPlay = function(resource, mimeType) {
703
+ const resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
704
+ const isHls = ((resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'm3u8') || listContainsIgnoreCase(mimeType, ['application/vnd.apple.mpegurl', 'application/x-mpegURL']))
705
+ return !!(HLSJS.isSupported() && isHls)
706
+ }