Skip to content

Commit

Permalink
feat: Add 'duringplaylistchange' event. (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
misteroneill committed Jan 25, 2018
1 parent 1b9e74b commit eb80503
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 1 deletion.
36 changes: 36 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,42 @@ Fires the `playlistsorted` event after shuffling.

## Events

### `duringplaylistchange`

This event is fired _after_ the contents of the playlist are changed when calling `playlist()`, but _before_ the current playlist item is changed. The event object has several special properties:

- `nextIndex`: The index from the next playlist that will be played first.
- `nextPlaylist`: A shallow clone of the next playlist.
- `previousIndex`: The index from the previous playlist (will always match the current index when this event triggers, but is provided for completeness).
- `previousPlaylist`: A shallow clone of the previous playlist.

#### Caveats

During the firing of this event, the playlist is considered to be in a **changing state**, which has the following effects:

- Calling the main playlist method (i.e. `player.playlist([...])`) will throw an error.
- Playlist navigation methods - `first`, `last`, `next`, and `previous` - are rendered inoperable.
- The `currentItem()` method only acts as a getter.
- While the sorting methods - `sort`, `reverse`, and `shuffle` - will continue to work, they do not fire the `playlistsorted` event.

#### Why have this event?

This event provides an opportunity to intercept the playlist setting process before a new source is set on the player and before the `playlistchange` event fires, while providing a consistent playlist API.

One use-case might be shuffling a playlist that has just come from a server, but before its initial source is loaded into the player or the playlist UI is updated:

```js
player.on('duringplaylistchange', function() {

// Remember, this will not trigger a "playlistsorted" event!
player.playlist.shuffle();
});

player.on('playlistchange', function() {
videojs.log('The playlist was shuffled, so the UI can be updated.');
});
```

### `playlistchange`

This event is fired asynchronously whenever the contents of the playlist are changed (i.e., when `player.playlist()` is called with an argument).
Expand Down
65 changes: 64 additions & 1 deletion src/playlist-maker.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const indexInSources = (arr, src) => {
*/
export default function factory(player, initialList, initialIndex = 0) {
let list = Array.isArray(initialList) ? initialList.slice() : [];
let changing = false;

/**
* Get/set the playlist for a player.
Expand All @@ -124,12 +125,35 @@ export default function factory(player, initialList, initialIndex = 0) {
* The playlist
*/
const playlist = player.playlist = (newList, newIndex = 0) => {
if (changing) {
throw new Error('do not call playlist() during a playlist change');
}

if (Array.isArray(newList)) {
const previousPlaylist = list.slice();

list = newList.slice();

// Mark the playlist as changing during the duringplaylistchange lifecycle.
changing = true;

player.trigger({
type: 'duringplaylistchange',
nextIndex: newIndex,
nextPlaylist: list,
previousIndex: playlist.currentIndex_,
previousPlaylist
});

changing = false;

if (newIndex !== -1) {
playlist.currentItem(newIndex);
}
player.setTimeout(() => player.trigger('playlistchange'), 0);

player.setTimeout(() => {
player.trigger('playlistchange');
}, 0);
}

// Always return a shallow clone of the playlist list.
Expand All @@ -151,13 +175,21 @@ export default function factory(player, initialList, initialIndex = 0) {
/**
* Get or set the current item in the playlist.
*
* During the duringplaylistchange event, acts only as a getter.
*
* @param {number} [index]
* If given as a valid value, plays the playlist item at that index.
*
* @return {number}
* The current item index.
*/
playlist.currentItem = (index) => {

// If the playlist is changing, only act as a getter.
if (changing) {
return playlist.currentIndex_;
}

if (
typeof index === 'number' &&
playlist.currentIndex_ !== index &&
Expand Down Expand Up @@ -291,6 +323,10 @@ export default function factory(player, initialList, initialIndex = 0) {
* Returns undefined and has no side effects if the list is empty.
*/
playlist.first = () => {
if (changing) {
return;
}

if (list.length) {
return list[playlist.currentItem(0)];
}
Expand All @@ -305,6 +341,10 @@ export default function factory(player, initialList, initialIndex = 0) {
* Returns undefined and has no side effects if the list is empty.
*/
playlist.last = () => {
if (changing) {
return;
}

if (list.length) {
return list[playlist.currentItem(playlist.lastIndex())];
}
Expand All @@ -319,6 +359,10 @@ export default function factory(player, initialList, initialIndex = 0) {
* Returns undefined and has no side effects if on last item.
*/
playlist.next = () => {
if (changing) {
return;
}

const index = playlist.nextIndex();

if (index !== playlist.currentIndex_) {
Expand All @@ -333,6 +377,10 @@ export default function factory(player, initialList, initialIndex = 0) {
* Returns undefined and has no side effects if on first item.
*/
playlist.previous = () => {
if (changing) {
return;
}

const index = playlist.previousIndex();

if (index !== playlist.currentIndex_) {
Expand Down Expand Up @@ -392,6 +440,11 @@ export default function factory(player, initialList, initialIndex = 0) {

list.sort(compare);

// If the playlist is changing, don't trigger events.
if (changing) {
return;
}

/**
* Triggered after the playlist is sorted internally.
*
Expand All @@ -416,6 +469,11 @@ export default function factory(player, initialList, initialIndex = 0) {

list.reverse();

// If the playlist is changing, don't trigger events.
if (changing) {
return;
}

/**
* Triggered after the playlist is sorted internally.
*
Expand Down Expand Up @@ -450,6 +508,11 @@ export default function factory(player, initialList, initialIndex = 0) {
list[index] = value;
}

// If the playlist is changing, don't trigger events.
if (changing) {
return;
}

/**
* Triggered after the playlist is sorted internally.
*
Expand Down
45 changes: 45 additions & 0 deletions test/playlist-maker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,51 @@ QUnit.test('loading a non-playlist video will cancel autoadvance and set index o
autoadvance.setReset_(oldReset);
});

QUnit.test('when loading a new playlist, trigger "duringplaylistchange" on the player', function(assert) {
const done = assert.async();
const player = playerProxyMaker();
const playlist = playlistMaker(player, [1, 2, 3], 1);

player.on('duringplaylistchange', (e) => {
assert.strictEqual(e.type, 'duringplaylistchange', 'the event object had the correct "type" property');
assert.strictEqual(e.previousIndex, 1, 'the event object had the correct "previousIndex" property');
assert.deepEqual(e.previousPlaylist, [1, 2, 3], 'the event object had the correct "previousPlaylist" property');
assert.strictEqual(e.nextIndex, 0, 'the event object had the correct "nextIndex" property');
assert.deepEqual(e.nextPlaylist, [4, 5, 6], 'the event object had the correct "nextPlaylist" property');

assert.throws(() => {
playlist([1, 2, 3]);
}, Error, 'cannot set a new playlist during a change');

const spy = sinon.spy();

player.on('playlistsorted', spy);
playlist.sort();
playlist.reverse();
playlist.shuffle();
assert.strictEqual(spy.callCount, 0, 'the "playlistsorted" event never fired');

playlist.currentItem(2);
assert.strictEqual(playlist.currentItem(), 1, 'the playlist current item could not be changed');

playlist.next();
assert.strictEqual(playlist.currentItem(), 1, 'the playlist current item could not be changed');

playlist.previous();
assert.strictEqual(playlist.currentItem(), 1, 'the playlist current item could not be changed');

playlist.first();
assert.strictEqual(playlist.currentItem(), 1, 'the playlist current item could not be changed');

playlist.last();
assert.strictEqual(playlist.currentItem(), 1, 'the playlist current item could not be changed');

done();
});

playlist([4, 5, 6]);
});

QUnit.test('when loading a new playlist, trigger "playlistchange" on the player', function(assert) {
const spy = sinon.spy();
const player = playerProxyMaker();
Expand Down

0 comments on commit eb80503

Please sign in to comment.