Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds a transient button component #8629

Merged
merged 13 commits into from
Jul 6, 2024
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ <h2>Navigation</h2>
<li><a href="sandbox/noUITitleAttributes.html">noUITitleAttributes Demo</a></li>
<li><a href="sandbox/docpip.html">Document Picture-In-Picture Demo</a></li>
<li><a href="sandbox/skip-buttons.html">Skip Buttons demo</a></li>
<li><a href="sandbox/transient-button.html">Transient Button demo</a></li>
<li><a href="sandbox/debug.html">Videojs debug build test page</a></li>
</ul>

Expand Down
121 changes: 121 additions & 0 deletions sandbox/transient-button.html.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Video.js Sandbox</title>
<link href="../dist/video-js.css" rel="stylesheet" type="text/css" />
<script src="../dist/video.js"></script>
<style>
article {
max-width: 800px;
margin: 0 auto;
}
.vjs-transient-button.unmute-button span::before {
content: "\f104";
font-family: "VideoJS";
vertical-align: middle;
padding-right: 0.3em;
}
</style>
</head>
<body>
<article>
<h1>Transient button demo</h1>
<video-js
id="vid1"
class="vjs-fluid"
controls
muted
preload="auto"
poster="https://vjs.zencdn.net/v/oceans.png"
>
<source src="https://vjs.zencdn.net/v/oceans.mp4" type="video/mp4" />
<source src="https://vjs.zencdn.net/v/oceans.webm" type="video/webm" />
<source src="https://vjs.zencdn.net/v/oceans.ogv" type="video/ogg" />
<track
kind="captions"
src="../docs/examples/shared/example-captions.vtt"
srclang="en"
label="English"
/>
</video-js>
</article>
<p>An unmute transient button will show after playback starts if muted.</p>
<p>
Transient buttons to skip into / credits / recap display at times defined
in a metadata track.
</p>
<script>
const player = videojs("#vid1");

player.ready(function () {
// Adds an unmute button that umutes and goes away when clicked
player.one("playing", function () {
if (this.muted()) {
const unmuteButton = player.addChild(
"TransientButton",
{
controlText: "Unmute",
position: ["top", "left"],
className: "unmute-button",
clickHandler: function () {
this.player().muted(false);
this.dispose();
},
},
player.children().indexOf(player.getChild("ControlBar"))
);
unmuteButton.show();
}
});

// A track that defines skippable parts
const track = player.addRemoteTextTrack({
src:
"data:text/vtt;base64," +
btoa(`WEBVTT

00:01.000 --> 00:10.000
Recap

00:15.000 --> 00:20.000
Intro

00:40.000 --> 00:47.000
Credits

`),
kind: "metadata",
label: "skip_sections",
}).track;

let skipButtons = [];

track.addEventListener("cuechange", function () {
const cue = track.activeCues[0];
if (cue) {
const skipButton = player.addChild(
"TransientButton",
{
controlText: `Skip ${cue.text}`,
position: ["bottom", "right"],
clickHandler: () => {
player.currentTime(cue.endTime);
},
takeFocus: true,
},
player.children().indexOf(player.getChild("ControlBar"))
);
skipButtons.push(skipButton);
skipButton.show();
} else {
while (skipButtons.length > 0) {
skipButtons.shift().dispose();
}
}
});
track.mode = "hidden";
});
</script>
</body>
</html>
48 changes: 48 additions & 0 deletions src/css/components/_transient-button.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.video-js .vjs-transient-button {
position: absolute;
height: 3em;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(50, 50, 50, 0.5);
cursor: pointer;
opacity: 1;
transition: opacity 1s;
}

.video-js:not(.vjs-has-started) .vjs-transient-button {
display: none;
}

.video-js.not-hover .vjs-transient-button:not(.force-display),
.video-js.vjs-user-inactive .vjs-transient-button:not(.force-display) {
opacity: 0;
}

.video-js .vjs-transient-button span {
padding: 0 0.5em;
}

.video-js .vjs-transient-button.vjs-left {
left: 1em;
}

.video-js .vjs-transient-button.vjs-right {
right: 1em;
}

.video-js .vjs-transient-button.vjs-top {
top: 1em;
}

.video-js .vjs-transient-button.vjs-near-top {
top: 4em;
}

.video-js .vjs-transient-button.vjs-bottom {
bottom: 4em;
}

.video-js .vjs-transient-button:hover {
background-color: rgba(50, 50, 50, 0.9);
}
1 change: 1 addition & 0 deletions src/css/video-js.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
@import "components/captions-settings";
@import "components/title-bar";
@import "components/skip-buttons";
@import "components/transient-button";

@import "print";

Expand Down
1 change: 1 addition & 0 deletions src/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import './tracks/text-track-settings.js';
import './resize-manager.js';
import './live-tracker.js';
import './title-bar.js';
import './transient-button.js';

// Import Html5 tech, at least for disposing the original video tag.
import './tech/html5.js';
Expand Down
124 changes: 124 additions & 0 deletions src/js/transient-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import Button from './button.js';
import Component from './component.js';
import {merge} from './utils/obj';
import * as Dom from './utils/dom.js';

/** @import Player from './player' */

/**
* @typedef {object} TransientButtonOptions
* @property {string} [controlText] Control text, usually visible for these buttons
* @property {number} [initialDisplay=4000] Time in ms that button should initially remain visible
* @property {Array<'top'|'neartop'|'bottom'|'left'|'right'>} [position] Array of position strings to add basic styles for positioning
* @property {string} [className] Class(es) to add
* @property {boolean} [takeFocus=false] Whether element sohuld take focus when shown
* @property {Function} [clickHandler] Function called on button activation
*/

/** @type {TransientButtonOptions} */
const defaults = {
initialDisplay: 4000,
position: [],
takeFocus: false
};

/**
* A floating transient button.
* It's recommended to insert these buttons _before_ the control bar with the this argument to `addChild`
* for a logical tab order.
*
* @example
* ```
* player.addChild(
* 'TransientButton',
* options,
* player.children().indexOf(player.getChild("ControlBar"))
* )
* ```
*
* @extends Button
*/
class TransientButton extends Button {
/**
* TransientButton constructor
*
* @param {Player} player The button's player
* @param {TransientButtonOptions} options Options for the transient button
*/
constructor(player, options) {
options = merge(defaults, options);
super(player, options);
this.controlText(options.controlText);
this.hide();

// When shown, the float button will be visible even if the user is inactive.
// Clear this if there is any interaction.
player.on(['useractive', 'userinactive'], (e) => {
this.removeClass('force-display');
});
}

/**
* Return CSS class including position classes
*
* @return {string} CSS class list
*/
buildCSSClass() {
return `vjs-transient-button focus-visible ${this.options_.position.map((c) => `vjs-${c}`).join(' ')}`;
}

/**
* Create the button element
*
* @return {HTMLButtonElement} The button element
*/
createEl() {
/** @type HTMLButtonElement */
const el = Dom.createEl(
'button', {}, {
type: 'button',
class: this.buildCSSClass()
},
Dom.createEl('span')
);

this.controlTextEl_ = el.querySelector('span');

return el;
}

/**
* Show the button. The button will remain visible for the `initialDisplay` time, default 4s,
* and when there is user activity.
*/
show() {
super.show();
this.addClass('force-display');
if (this.options_.takeFocus) {
this.el().focus({ preventScroll: true});
}

this.forceDisplayTimeout = this.player_.setTimeout(() => {
this.removeClass('force-display');
}, this.options_.initialDisplay);
}

/**
* Hide the display, even if during the `initialDisplay` time.
*/
hide() {
this.removeClass('force-display');
super.hide();
}

/**
* Dispose the component
*/
dispose() {
this.player_.clearTimeout(this.forceDisplayTimeout);
super.dispose();
}
}

Component.registerComponent('TransientButton', TransientButton);
export default TransientButton;
Loading