Skip to content

Commit

Permalink
feat(playlist-plugin): enhance configuration options for playlist modal
Browse files Browse the repository at this point in the history
Resolves #16 by introducing new configuration options that allow integrators to specify which
buttons should appear in the playlist modal. Also exposes the modal options.

- Added `modal` option to configure the modal dialog component.
- Exposed `modal.pauseOnOpen` option to control whether the player pauses when the modal is opened.
- Added `modal.controls` option to define the order, addition, or removal of the control buttons.

***Other Changes***

- Split the `controls` into individual components that can be registered separately.
- Added a test for the playlist plugin UI registration.
  • Loading branch information
jboix committed Jun 10, 2024
1 parent 917ed75 commit 0239cb9
Show file tree
Hide file tree
Showing 14 changed files with 463 additions and 265 deletions.
45 changes: 44 additions & 1 deletion packages/pillarbox-playlist/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import '@srgssr/pillarbox-playlist/ui';
const player = new Pillarbox('my-player', {
plugins: {
pillarboxPlaylist: { autoadvance: true, repeat: true },
pillarboxPlaylistUI: { insertChildBefore: 'fullscreenToggle' }
pillarboxPlaylistUI: true
}
});

Expand Down Expand Up @@ -144,6 +144,49 @@ player.playlistPlugin().on('statechanged', ({ changes }) => {
});
```

#### User Interface

As seen before, this library contains an additional plugin that provides a customizable user
interface for the playlist.

##### Options

When initializing the playlist-ui plugin, you can pass an `options` object that configures the
behavior of the plugin. Here are the available options:

| Option | Type | Default | Description |
|---------------------|---------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `insertChildBefore` | String | `fullscreenToggle` | The control bar child name before which the playlist button should be inserted. |
| `modal` | Object | `{}` | Configuration for the modal dialog component. This can take any modal dialog options available in video.js. [See Video.js ModalDialog Documentation](https://docs.videojs.com/tutorial-modal-dialog.html) |
| `modal.pauseOnOpen` | Boolean | `false` | If true, the player will pause when the modal dialog is opened. |
| `modal.controls` | Object | `{}` | Configuration for the control buttons within the modal. You can define the order of the buttons, remove buttons you don't need, or add new ones. [See Video.js Component Children Documentation](https://videojs.com/guides/components/#component-children) |

***Example Usage***

```javascript
import Pillarbox from '@srgssr/pillarbox-web';
import '@srgssr/pillarbox-playlist';
import '@srgssr/pillarbox-playlist/ui';

const player = new Pillarbox('my-player', {
plugins: {
// Include the playlist plugin
pillarboxPlaylist: true,
// Include the playlist UI plugin
pillarboxPlaylistUI: {
// Change the placement of the playlist button
inserChildBefore: 'subsCapsButton',
modal: {
// Force the plaback to pause when the modal is opened
pauseOnOpen: true,
// Remove the shuffle button
controls: { shuffleButton: false }
}
}
}
});
```

## Contributing

For detailed contribution guidelines, refer to the main project’s [README file][main-readme]. Please
Expand Down
4 changes: 2 additions & 2 deletions packages/pillarbox-playlist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
autoplay: true,
srgOptions: { dataProviderHost: ilHost },
plugins: {
pillarboxPlaylist: { autoadvance: true, repeat: true },
pillarboxPlaylistUI: { insertChildBefore: 'fullscreenToggle' }
pillarboxPlaylist: { repeat: true },
pillarboxPlaylistUI: true
}
});

Expand Down
52 changes: 52 additions & 0 deletions packages/pillarbox-playlist/src/components/next-item-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import videojs from 'video.js';

/**
* @ignore
* @type {typeof import('video.js/dist/types/button').default}
*/
const Button = videojs.getComponent('Button');


/**
* The next item button for the playlist ui. When clicked moves to the
* next item in the playlist.
*/
class NextItemButton extends Button {
constructor(player, options) {
super(player, options);
this.handleLanguagechange();
this.setIcon('next-item');

this.player().ready(() => {
this.$('.vjs-icon-placeholder').classList.toggle(`vjs-icon-next-item`, true);
});
}

/**
* Get the playlist instance associated with the player.
*
* @returns {import('pillarbox-playlist.js').default} The playlist instance.
*/
playlist() {
return this.player().pillarboxPlaylist();
}

/**
* Handles the click event on the button.
*
* @param {Event} event - The click event.
*/
handleClick(event) {
super.handleClick(event);
this.playlist().next();
}

/**
* Handles the language change event to update the control text.
*/
handleLanguagechange() {
this.controlText(this.localize('Next Item'));
}
}

videojs.registerComponent('NextItemButton', NextItemButton);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import videojs from 'video.js';
import './pillarbox-playlist-modal.js';
import './lang';
import '../lang/index.js';

/**
* @ignore
Expand Down Expand Up @@ -37,7 +37,7 @@ class PillarboxPlaylistButton extends Button {
this.setIcon('chapters');
player.ready(() => {
this.$('.vjs-icon-placeholder').classList.toggle('vjs-icon-chapters', true);
player.addChild('PlaylistMenuDialog', {pauseOnOpen: false});
player.addChild('PlaylistMenuDialog', options.modal);
});

this.playlist().on('statechanged', this._onPlaylistStateChanged);
Expand All @@ -54,7 +54,7 @@ class PillarboxPlaylistButton extends Button {
/**
* Get the playlist instance associated with the player.
*
* @returns {import('pillarbox-playlist.js').default} The playlist instance.
* @returns {import('packages/pillarbox-playlist/src/pillarbox-playlist.js').default} The playlist instance.
*/
playlist() {
return this.player().pillarboxPlaylist();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class PillarboxPlaylistMenuItem extends Button {
/**
* Gets the Pillarbox playlist associated with the player.
*
* @returns {import('/pillarbox-playlist.js').default} The Pillarbox playlist.
* @returns {import('/packages/pillarbox-playlist/src/pillarbox-playlist.js').default} The Pillarbox playlist.
*/
playlist() {
return this.player().pillarboxPlaylist();
Expand Down
138 changes: 138 additions & 0 deletions packages/pillarbox-playlist/src/components/pillarbox-playlist-modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import videojs from 'video.js';
import './pillarbox-playlist-menu-item.js';
import './playlist-controls.js';

/**
* @ignore
* @type {typeof import('./pillarbox-playlist-menu-item.js').default}
*/
const PillarboxPlaylistMenuItem = videojs.getComponent('PillarboxPlaylistMenuItem');
/**
* @ignore
* @type {typeof import('video.js/dist/types/component').default}
*/
const Component = videojs.getComponent('Component');
/**
* @ignore
* @type {typeof import('video.js/dist/types/modal-dialog').default}
*/
const ModalDialog = videojs.getComponent('ModalDialog');

/**
* PlaylistMenuDialog is a custom dialog that extends the ModalDialog class.
* It is designed to manage and display a playlist with various controls.
*/
class PlaylistMenuDialog extends ModalDialog {
/**
* Handles the 'statechanged' event when triggered by the playlist. This method
* serves as a proxy to the main `statechanged` handler, ensuring that additional
* logic can be executed or making it easier to detach the event listener later.
*
* @private
*/
_onPlaylistStateChanged = ({ changes }) => {
if ('items' in changes) {
this.removeItems();
this.renderItems();
}

if ('currentIndex' in changes) {
this.select(changes.currentIndex.to);
}
};

/**
* Creates an instance of PlaylistMenuDialog.
*
* @param {import('@srgssr/pillarbox-web').Player} player - The pillarbox player instance.
* @param {Object} options - Options for the dialog.
* @param {boolean} [options.pauseOnOpen=false] - If true, the player will pause when the modal dialog is opened.
* @param {Object} [options.controls={}] - Configuration for the control buttons within the modal. You can define the order of the buttons, remove buttons you don't need, or add new ones.
*/
constructor(player, options) {
options.temporary = false;
options = videojs.mergeOptions({ pauseOnOpen: false }, options);

super(player, options);

this.fill();
this.addChild('PlaylistControls', options.controls);
this.renderItems();
this.playlist().on('statechanged', this._onPlaylistStateChanged);
}

buildCSSClass() {
return `pbw-playlist-dialog ${super.buildCSSClass()}`;
}

/**
* Dispose of the PlaylistMenuDialog instance.
*/
dispose() {
this.playlist().off('statechanged', this._onPlaylistStateChanged);
super.dispose();
}

/**
* Get the playlist instance associated with the player.
*
* @returns {import('packages/pillarbox-playlist/src/pillarbox-playlist.js').default} The playlist instance.
*/
playlist() {
return this.player().pillarboxPlaylist();
}

/**
* Update the playlist item UI with the selected index.
*
* @param {number} index - The index of the item to select.
*/
select(index) {
const itemList = this.getChild('PillarboxPlaylistMenuItemsList');

itemList.children()
.filter(item => item.name() === 'PillarboxPlaylistMenuItem')
.map(item => item.getChild('PillarboxPlaylistMenuItemButton'))
.forEach(button => button.selected(index === button.options().index));
}

/**
* Remove all playlist items from the dialog.
*/
removeItems() {
this.removeChild(this.getChild('PillarboxPlaylistMenuItemsList'));
}

/**
* Render the playlist items in the dialog.
*/
renderItems() {
const itemListEl = new Component(this.player(), {
name: 'PillarboxPlaylistMenuItemsList',
el: videojs.dom.createEl('ol', {
className: 'pbw-playlist-items'
})
});

this.playlist().items.forEach((item, index) => {
const itemEl = new Component(this.player(), {
name: 'PillarboxPlaylistMenuItem',
el: videojs.dom.createEl('li', {
className: 'pbw-playlist-item'
})
});

itemEl.addChild(new PillarboxPlaylistMenuItem(this.player(), {
item,
index,
name: 'PillarboxPlaylistMenuItemButton'
}));

itemListEl.addChild(itemEl);
});

this.addChild(itemListEl);
}
}

videojs.registerComponent('PlaylistMenuDialog', PlaylistMenuDialog);
27 changes: 27 additions & 0 deletions packages/pillarbox-playlist/src/components/playlist-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import videojs from 'video.js';
import './next-item-button.js';
import './previous-item-button.js';
import './repeat-button.js';
import './shuffle-button.js';

/**
* @ignore
* @type {typeof import('video.js/dist/types/component').default}
*/
const Component = videojs.getComponent('Component');

class PlaylistControls extends Component {

}

PlaylistControls.prototype.options_ = {
className: 'pbw-playlist-controls',
children: [
'repeatButton',
'shuffleButton',
'previousItemButton',
'nextItemButton'
]
};

videojs.registerComponent('PlaylistControls', PlaylistControls);
51 changes: 51 additions & 0 deletions packages/pillarbox-playlist/src/components/previous-item-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import videojs from 'video.js';

/**
* @ignore
* @type {typeof import('video.js/dist/types/button').default}
*/
const Button = videojs.getComponent('Button');

/**
* The previous item button for the playlist ui. When clicked moves to the
* previous item in the playlist.
*/
class PreviousItemButton extends Button {
constructor(player, options) {
super(player, options);
this.handleLanguagechange();
this.setIcon('previous-item');

this.player().ready(() => {
this.$('.vjs-icon-placeholder').classList.toggle(`vjs-icon-previous-item`, true);
});
}

/**
* Get the playlist instance associated with the player.
*
* @returns {import('pillarbox-playlist.js').default} The playlist instance.
*/
playlist() {
return this.player().pillarboxPlaylist();
}

/**
* Handles the click event on the button.
*
* @param {Event} event - The click event.
*/
handleClick(event) {
super.handleClick(event);
this.playlist().previous();
}

/**
* Handles the language change event to update the control text.
*/
handleLanguagechange() {
this.controlText(this.localize('Previous Item'));
}
}

videojs.registerComponent('PreviousItemButton', PreviousItemButton);
Loading

0 comments on commit 0239cb9

Please sign in to comment.