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 `pillarboxPlaylistMenuDialog` option to configure the modal dialog component.
- Exposed `pillarboxPlaylistMenuDialog.pauseOnOpen` option to control whetherWQ the player
  pauses when the modal is opened.
- Added `pillarboxPlaylistMenuDialog.pillarboxPlaylistControls` option to define the order,
  addition, or remova 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.
- Added suffix `pillarboxPlaylist` to all components to avoid naming clashes.
  • Loading branch information
jboix committed Jul 11, 2024
1 parent eff1406 commit 8f47a25
Show file tree
Hide file tree
Showing 17 changed files with 741 additions and 415 deletions.
45 changes: 44 additions & 1 deletion packages/pillarbox-playlist/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,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 @@ -133,6 +133,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. |
| `pillarboxPlaylistMenuDialog` | 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) |
| `pillarboxPlaylistMenuDialog.pauseOnOpen` | Boolean | `false` | If true, the player will pause when the modal dialog is opened. |
| `pillarboxPlaylistMenuDialog.pillarboxPlaylistControls` | 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',
pillarboxPlaylistMenuDialog: {
// Force the playback to pause when the modal is opened
pauseOnOpen: true,
// Remove the shuffle button
pillarboxPlaylistControls: { pillarboxPlaylistShuffleButton: 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import videojs from 'video.js';
import './pillarbox-playlist-next-item-button.js';
import './pillarbox-previous-item-button.js';
import './pillarbox-playlist-repeat-button.js';
import './pillarbox-playlist-shuffle-button.js';

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

class PillarboxPlaylistControls extends Component {

}

PillarboxPlaylistControls.prototype.options_ = {
className: 'pbw-playlist-controls',
children: [
'pillarboxPlaylistRepeatButton',
'pillarboxPlaylistShuffleButton',
'pillarboxPlaylistPreviousItemButton',
'pillarboxPlaylistNextItemButton'
]
};

videojs.registerComponent('PillarboxPlaylistControls', PillarboxPlaylistControls);
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import videojs from 'video.js';

/**
* @ignore
* @type {typeof import('video.js/dist/types/menu/menu-item').default}
* @type {typeof import('../pillarbox-playlist-base.js').PillarboxPlaylistBaseButton}
*/
const Button = videojs.getComponent('Button');
const Button = videojs.getComponent('PillarboxPlaylistBaseButton');

/**
* Class representing a playlist menu item in the Pillarbox plugin.
Expand All @@ -26,15 +26,6 @@ class PillarboxPlaylistMenuItem extends Button {
this.controlText(`${this.options_.item.data?.title} - ${videojs.formatTime(this.options_.item.data?.duration)}`);
}

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

/**
* Handles the click event on the menu item.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import videojs from 'video.js';
import './pillarbox-playlist-menu-item.js';
import './pillarbox-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('../pillarbox-playlist-base.js').PillarboxPlaylistBaseModal}
*/
const ModalDialog = videojs.getComponent('PillarboxPlaylistBaseModal');

/**
* 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.pillarboxPlaylistControls={}] - 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('PillarboxPlaylistControls', options.pillarboxPlaylistControls);
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();
}

/**
* 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('PillarboxPlaylistMenuDialog', PlaylistMenuDialog);
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import videojs from 'video.js';

/**
* @ignore
* @type {typeof import('../pillarbox-playlist-base.js').PillarboxPlaylistBaseButton}
*/
const Button = videojs.getComponent('PillarboxPlaylistBaseButton');

/**
* The next item button for the playlist ui. When clicked moves to the
* next item in the playlist.
*/
class PillarboxPlaylistNextItemButton extends Button {
constructor(player, options) {
options = videojs.mergeOptions({ controlText: 'Next Item' }, options);
super(player, options);
this.setIcon('next-item');
}

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

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

videojs.registerComponent('PillarboxPlaylistNextItemButton', PillarboxPlaylistNextItemButton);
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import videojs from 'video.js';

/**
* @ignore
* @type {typeof import('../pillarbox-playlist-base.js').PillarboxPlaylistBaseButton}
*/
const Button = videojs.getComponent('PillarboxPlaylistBaseButton');

/**
* The repeat button for the playlist ui. When clicked toggles the repeat mode
* of the playlist.
*/
class PillarboxPlaylistRepeatButton extends Button {
constructor(player, options) {
options = videojs.mergeOptions({ controlText: 'Repeat' }, options);
super(player, options);
this.setIcon('repeat');
}

ready() {
this.$('.vjs-icon-placeholder').classList.toggle(`vjs-icon-repeat`, true);
}

/**
* Builds the CSS class string for the button.
*
* @returns {string} The CSS class string.
*/
buildCSSClass() {
return `${this.playlist().repeat ? 'vjs-selected' : ''} ${super.buildCSSClass()}`;
}

/**
* Handles the click event on the button.
*
* @param {Event} event - The click event.
*/
handleClick(event) {
super.handleClick(event);
this.playlist().toggleRepeat();
this.toggleClass('vjs-selected', this.playlist().repeat);
}
}

videojs.registerComponent('PillarboxPlaylistRepeatButton', PillarboxPlaylistRepeatButton);
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import videojs from 'video.js';

/**
* @ignore
* @type {typeof import('../pillarbox-playlist-base.js').PillarboxPlaylistBaseButton}
*/
const Button = videojs.getComponent('PillarboxPlaylistBaseButton');

/**
* The shuffle button for the playlist ui. When clicked shuffles the items
* in the playlist.
*/
class PillarboxPlaylistShuffleButton extends Button {
constructor(player, options) {
options = videojs.mergeOptions({ controlText: 'Shuffle' }, options);
super(player, options);
this.setIcon('shuffle');
}

ready() {
this.$('.vjs-icon-placeholder').classList.toggle(`vjs-icon-shuffle`, true);
}

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

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

0 comments on commit 8f47a25

Please sign in to comment.