Skip to content

Commit

Permalink
add example
Browse files Browse the repository at this point in the history
  • Loading branch information
mister-ben committed Jun 18, 2024
1 parent b62774e commit ebebbde
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 60 deletions.
112 changes: 65 additions & 47 deletions sandbox/transient-button.html.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,71 +5,86 @@
<title>Video.js Sandbox</title>
<link href="../dist/video-js.css" rel="stylesheet" type="text/css" />
<script src="../dist/video.js"></script>
</head>
<body>
<video-js
id="vid1"
controls
muted
preload="auto"
width="640"
height="264"
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>

<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();
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();
console.log(unmuteButton.el());
}
});

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

00:00.000 --> 00:10.000
Recap
00:01.000 --> 00:10.000
Recap

00:15.000 --> 00:20.000
Intro
00:15.000 --> 00:20.000
Intro

00:40.000 --> 00:47.000
Credits
00:40.000 --> 00:47.000
Credits

`),
`),
kind: "metadata",
label: "skip_sections",
}).track;
Expand All @@ -78,16 +93,19 @@ Credits

track.addEventListener("cuechange", function () {
const cue = track.activeCues[0];
console.log(cue);
if (cue) {
const skipButton = player.addChild("TransientButton", {
controlText: `Skip ${cue.text}`,
position: "bottom right",
clickHandler: () => {
player.currentTime(cue.endTime);
const skipButton = player.addChild(
"TransientButton",
{
controlText: `Skip ${cue.text}`,
position: ["bottom", "right"],
clickHandler: () => {
player.currentTime(cue.endTime);
},
takeFocus: true,
},
takeFocus: true
});
player.children().indexOf(player.getChild("ControlBar"))
);
skipButtons.push(skipButton);
skipButton.show();
} else {
Expand Down
10 changes: 5 additions & 5 deletions src/css/components/_transient-button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
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;
}
Expand Down Expand Up @@ -41,8 +46,3 @@
.video-js .vjs-transient-button:hover {
background-color: rgba(50, 50, 50, 0.9);
}

.video-js.not-hover .vjs-transient-button:not(.force-display),
.video-js.vjs-user-inactive .vjs-transient-button:not(.force-display) {
opacity: 0;
}
17 changes: 9 additions & 8 deletions src/js/transient-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ import * as Dom from './utils/dom.js';

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

const defaults = {
initialDisplay: 4000,
position: [],
takeFocus: false
};

/**
* @typedef {object} TransientButtonOptions
* @property {string} [controlText] Control text, usually visible for these buttons
Expand All @@ -21,6 +15,13 @@ const defaults = {
* @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 for a logic tab order.
Expand All @@ -40,7 +41,7 @@ class TransientButton extends Button {

// 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'], () => {
player.on(['useractive', 'userinactive'], (e) => {
this.removeClass('force-display');
});
}
Expand Down Expand Up @@ -77,7 +78,7 @@ class TransientButton extends Button {

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

/**
Expand Down
89 changes: 89 additions & 0 deletions test/unit/transient-button.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* eslint-env qunit */
import TransientButton from '../../src/js/transient-button.js';
import TestHelpers from './test-helpers.js';
import sinon from 'sinon';

QUnit.module('TransientButton');

QUnit.only('show and hide should add and remove force-display class', function(assert) {
const player = TestHelpers.makePlayer();

const testButton = new TransientButton(player, {});

player.addChild(testButton);

assert.false(testButton.hasClass('force-display'), 'button is initially hidden');

testButton.show();
assert.true(testButton.hasClass('force-display'), 'button has force-display after show()');

testButton.hide();
assert.false(testButton.hasClass('force-display'), 'button no longer has force-display after hide()');

player.dispose();
});

QUnit.only('show and hide should add and remove force-display class', function(assert) {
this.clock = sinon.useFakeTimers();

const player = TestHelpers.makePlayer();

const testButton = new TransientButton(player, {});

player.hasStarted(true);
player.userActive(false);

player.addChild(testButton);

assert.false(testButton.hasClass('force-display'), 'button is initially hidden');

testButton.show();
assert.true(testButton.hasClass('force-display'), 'button has force-display after show()');

this.clock.tick(2000);
assert.true(testButton.hasClass('force-display'), 'button still has force-display until timeout');

this.clock.tick(2500);
assert.false(testButton.hasClass('force-display'), 'button no longer has force-display until timeout');

player.dispose();

this.clock.restore();
});

QUnit.only('applies posiiton classes', function(assert) {
const player = TestHelpers.makePlayer();
const testButton1 = new TransientButton(player, { position: ['top', 'left']});
const testButton2 = new TransientButton(player, { position: ['bottom', 'right']});
const testButton3 = new TransientButton(player, {});

assert.ok(testButton1.hasClass('vjs-top'), 'position top yields vjs-top class');
assert.ok(testButton1.hasClass('vjs-left'), 'position left yields vjs-left class');
assert.ok(testButton2.hasClass('vjs-bottom'), 'position bottom yields vjs-bottom class');
assert.ok(testButton2.hasClass('vjs-right'), 'position right yields vjs-right class');
['vjs-top', 'vjs-neartop', 'vjs-bottom', 'vjs-left', 'vjs-right'].forEach(positionClass => {
assert.false(testButton3.hasClass(positionClass), `with no options should be no ${positionClass} class`);
});

player.dispose();
});

QUnit.only('takes focus only when specified', function(assert) {

const player = TestHelpers.makePlayer();
const testButton1 = new TransientButton(player, {});
const testButton2 = new TransientButton(player, {takeFocus: true});

const spy1 = sinon.spy(testButton1.el_, 'focus');
const spy2 = sinon.spy(testButton2.el_, 'focus');

player.addChild(testButton1);
testButton1.show();
assert.false(spy1.called, 'by default a button should not take focus');

player.addChild(testButton2);
testButton2.show();
assert.true(spy2.called, 'when enabled button should take focus');

player.dispose();
});

0 comments on commit ebebbde

Please sign in to comment.