Skip to content

Commit

Permalink
Merge pull request #15 from clappr/decouple-setup-from-load-source
Browse files Browse the repository at this point in the history
Decouple setup playback process from the load source process
  • Loading branch information
joaopaulovieira authored May 13, 2021
2 parents 5baf051 + b90c5ae commit d4d3023
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 47 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ var player = new Clappr.Player(
hlsUseNextLevel: false,
hlsMinimumDvrSize: 60,
hlsRecoverAttempts: 16,
hlsPlayback: {
preload: true,
},
playback: {
extrapolatedWindowNumSegments: 2,
triggerFatalErrorOnResourceDenied: false,
Expand Down Expand Up @@ -83,6 +86,26 @@ The `hls.js` have recover approaches for some fatal errors. This option sets the
If this option is set to true, the playback will triggers fatal error event if decrypt key http response code is greater than or equal to 400. This option is used to attempt to reproduce iOS devices behaviour which internally use html5 video playback.

#### hlsPlayback
> Soon (in a new breaking change version), all options related to this playback that are declared in the scope of the `options` object will have to be declared necessarily within this new scope!
Groups all options related directly to `HlsjsPlayback` configs.

```javascript
var player = new Clappr.Player(
{
...
hlsPlayback: {
preload: true,
},
});
```

#### `hlsPlayback.preload`
> Default value: `true`
Configures whether the source should be loaded as soon as the `HLS.JS` internal reference is setup or only after the first play.

#### hlsjsConfig

As `HlsjsPlayback` is based on `hls.js`, it's possible to use the available `hls.js` configs too. You can check them out [here](https://github.com/video-dev/hls.js/blob/master/docs/API.md#fine-tuning).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"watch": "rollup --config --watch",
"bundle-check": "ANALYZE_BUNDLE=true rollup --config",
"release": "MINIMIZE=true rollup --config",
"test": "jest ./src --coverage",
"test": "jest ./src --coverage --silent",
"test:debug": "node --inspect node_modules/.bin/jest ./src --runInBand",
"test:watch": "jest ./src --watch",
"lint": "eslint *.js ./src",
Expand Down
13 changes: 10 additions & 3 deletions src/hls.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ export default class HlsjsPlayback extends HTML5Video {
return this._hls && this._hls.bandwidthEstimate
}

get defaultOptions() {
return { preload: true }
}

static get HLSJS() {
return HLSJS
}
Expand All @@ -117,6 +121,7 @@ export default class HlsjsPlayback extends HTML5Video {
super(...args)
// backwards compatibility (TODO: remove on 0.3.0)
this.options.playback = { ...this.options, ...this.options.playback }
this.options.hlsPlayback = { ...this.defaultOptions, ...this.options.hlsPlayback }
this._minDvrSize = typeof (this.options.hlsMinimumDvrSize) === 'undefined' ? 60 : this.options.hlsMinimumDvrSize
// The size of the start time extrapolation window measured as a multiple of segments.
// Should be 2 or higher, or 0 to disable. Should only need to be increased above 2 if more than one segment is
Expand Down Expand Up @@ -158,11 +163,13 @@ export default class HlsjsPlayback extends HTML5Video {
}

_setup() {
this._manifestParsed = false
this._ccIsSetup = false
this._ccTracksUpdated = false
this._hls && this._hls.destroy()
this._hls = new HLSJS(assign({}, this.options.playback.hlsjsConfig))
this._hls.once(HLSJS.Events.MEDIA_ATTACHED, () => this._hls.loadSource(this.options.src))
this._hls.once(HLSJS.Events.MEDIA_ATTACHED, () => { this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src) })
this._hls.on(HLSJS.Events.MANIFEST_PARSED, () => this._manifestParsed = true)
this._hls.on(HLSJS.Events.LEVEL_LOADED, (evt, data) => this._updatePlaybackType(evt, data))
this._hls.on(HLSJS.Events.LEVEL_UPDATED, (evt, data) => this._onLevelUpdated(evt, data))
this._hls.on(HLSJS.Events.LEVEL_SWITCHING, (evt,data) => this._onLevelSwitch(evt, data))
Expand Down Expand Up @@ -416,8 +423,8 @@ export default class HlsjsPlayback extends HTML5Video {
}

play() {
if (!this._hls)
this._setup()
!this._hls && this._setup()
!this._manifestParsed && !this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src)

super.play()
this._startTimeUpdateTimer()
Expand Down
153 changes: 110 additions & 43 deletions src/hls.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@ import { Core, Events } from '@clappr/core'
import HlsjsPlayback from './hls.js'
import HLSJS from 'hls.js'

describe('HLS playback', () => {
const simplePlaybackMock = new HlsjsPlayback({ src: 'http://clappr.io/video.m3u8' })

describe('HlsjsPlayback', () => {
test('have a getter called template', () => {
expect(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(simplePlaybackMock), 'defaultOptions').get).toBeTruthy()
})

test('defaultOptions getter returns all the default options values into one object', () => {
expect(simplePlaybackMock.defaultOptions).toEqual({ preload: true })
})

test('should be able to identify it can play resources independently of the file extension case', () => {
jest.spyOn(HLSJS, 'isSupported').mockImplementation(() => true)
expect(HlsjsPlayback.canPlay('/relative/video.m3u8')).toBeTruthy()
Expand Down Expand Up @@ -33,48 +43,6 @@ describe('HLS playback', () => {
expect(playback.tagName).toEqual('audio')
})

describe('options backwards compatibility', () => {
// backwards compatibility (TODO: remove on 0.3.0)
test('should set options.playback as a reference to options if options.playback not set', () => {
let options = { src: 'http://clappr.io/video.m3u8' },
hls = new HlsjsPlayback(options)
expect(hls.options.playback).toEqual(hls.options)
options = { src: 'http://clappr.io/video.m3u8', playback: { test: true } }
hls = new HlsjsPlayback(options)
expect(hls.options.playback.test).toEqual(true)
})
})

describe('HlsjsPlayback.js configuration', () => {
test('should use hlsjsConfig from playback options', () => {
const options = {
src: 'http://clappr.io/video.m3u8',
playback: {
hlsMinimumDvrSize: 1,
hlsjsConfig: {
someHlsjsOption: 'value'
}
}
}
const playback = new HlsjsPlayback(options)
playback._setup()
expect(playback._hls.config.someHlsjsOption).toEqual('value')
})

test('should use hlsjsConfig from player options as fallback', () => {
const options = {
src: 'http://clappr.io/video.m3u8',
hlsMinimumDvrSize: 1,
hlsjsConfig: {
someHlsjsOption: 'value'
}
}
const playback = new HlsjsPlayback(options)
playback._setup()
expect(playback._hls.config.someHlsjsOption).toEqual('value')
})
})

test('should trigger a playback error if source load failed', () => {
jest.spyOn(window.HTMLMediaElement.prototype, 'play').mockImplementation(() => {})
let resolveFn = undefined
Expand Down Expand Up @@ -127,4 +95,103 @@ describe('HLS playback', () => {
expect(playback.currentLevel).toEqual(1)
expect(playback._hls.currentLevel).toEqual(1)
})

describe('constructor', () => {
test('should use hlsjsConfig from playback options', () => {
const options = {
src: 'http://clappr.io/video.m3u8',
playback: {
hlsMinimumDvrSize: 1,
hlsjsConfig: {
someHlsjsOption: 'value'
}
}
}
const playback = new HlsjsPlayback(options)
playback._setup()
expect(playback._hls.config.someHlsjsOption).toEqual('value')
})

test('should use hlsjsConfig from player options as fallback', () => {
const options = {
src: 'http://clappr.io/video.m3u8',
hlsMinimumDvrSize: 1,
hlsjsConfig: {
someHlsjsOption: 'value'
}
}
const playback = new HlsjsPlayback(options)
playback._setup()
expect(playback._hls.config.someHlsjsOption).toEqual('value')
})

test('merges defaultOptions with received options.hlsPlayback', () => {
const options = {
src: 'http://clappr.io/foo.m3u8',
hlsPlayback: { foo: 'bar' },
}
const playback = new HlsjsPlayback(options)
expect(playback.options.hlsPlayback).toEqual({ ...options.hlsPlayback, ...playback.defaultOptions })
})
})

describe('_setup method', () => {
test('sets _manifestParsed flag to false', () => {
const playback = new HlsjsPlayback({ src: 'http://clappr.io/foo.m3u8' })
expect(playback._manifestParsed).toBeUndefined()

playback._setup()

expect(playback._manifestParsed).toBeFalsy()
})

test('calls this._hls.loadSource when MEDIA_ATTACHED event is triggered and hlsPlayback.preload is true', () => {
const playback = new HlsjsPlayback({ src: 'http://clappr.io/foo.m3u8', hlsPlayback: { preload: false } })
playback._setup()
jest.spyOn(playback._hls, 'loadSource')
playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHED)

expect(playback._hls.loadSource).not.toHaveBeenCalled()

playback.options.hlsPlayback.preload = true
playback._setup()
jest.spyOn(playback._hls, 'loadSource')
playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHED)

expect(playback._hls.loadSource).toHaveBeenCalledTimes(1)
})

test('updates _manifestParsed flag value to true if MANIFEST_PARSED event is triggered', () => {
const playback = new HlsjsPlayback({ src: 'http://clappr.io/foo.m3u8' })

expect(playback._manifestParsed).toBeUndefined()

playback._setup()
playback._hls.trigger(HLSJS.Events.MANIFEST_PARSED)

expect(playback._manifestParsed).toBeTruthy()
})
})

describe('play method', () => {
test('calls this._hls.loadSource if _manifestParsed flag and options.hlsPlayback.preload are falsy', () => {
const playback = new HlsjsPlayback({ src: 'http://clappr.io/foo.m3u8', hlsPlayback: { preload: true } })
playback._setup()
jest.spyOn(playback._hls, 'loadSource')
playback.play()

expect(playback._hls.loadSource).not.toHaveBeenCalled()

playback.options.hlsPlayback.preload = false
playback._manifestParsed = true
playback.play()

expect(playback._hls.loadSource).not.toHaveBeenCalled()

playback._manifestParsed = false
playback.play()

expect(playback._hls.loadSource).toHaveBeenCalledTimes(1)
})
})
})

0 comments on commit d4d3023

Please sign in to comment.