From ba312b23c67264beaf1288afec6896b24b68120c Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Thu, 2 Apr 2020 14:28:47 +0100 Subject: [PATCH 01/20] Structural code for video trimming UI --- controls.md | 3 + readme.md | 13 +- src/js/config/defaults.js | 23 +++ src/js/controls.js | 16 ++- src/js/listeners.js | 35 ++++- src/js/plugins/trim.js | 229 ++++++++++++++++++++++++++++++ src/js/plyr.d.ts | 13 ++ src/js/plyr.js | 19 +++ src/sass/components/controls.scss | 2 + src/sass/components/trim.scss | 25 ++++ src/sass/plugins/trim.scss | 49 +++++++ src/sass/plyr.scss | 2 + 12 files changed, 424 insertions(+), 5 deletions(-) create mode 100644 src/js/plugins/trim.js create mode 100644 src/sass/components/trim.scss create mode 100644 src/sass/plugins/trim.scss diff --git a/controls.md b/controls.md index b7b08c58c..d5a09044b 100644 --- a/controls.md +++ b/controls.md @@ -28,6 +28,7 @@ controls: [ 'settings', // Settings menu 'pip', // Picture-in-picture (currently Safari only) 'airplay', // Airplay (currently Safari only) + 'trim', // Trim Control 'download', // Show a download button with a link to either the current source or a custom URL you specify in your options 'fullscreen', // Toggle fullscreen ]; @@ -56,6 +57,8 @@ i18n: { unmute: 'Unmute', enableCaptions: 'Enable captions', disableCaptions: 'Disable captions', + enterTrim: 'Enter trim', + exitTrim: 'Exit trim', enterFullscreen: 'Enter fullscreen', exitFullscreen: 'Exit fullscreen', frameTitle: 'Player for {title}', diff --git a/readme.md b/readme.md index a5acb614a..ee4a55666 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ Plyr is a simple, lightweight, accessible and customizable HTML5, YouTube and Vi [Checkout the demo](https://plyr.io) - [Donate](#donate) - [Slack](https://bit.ly/plyr--chat) -[![npm version](https://badge.fury.io/js/plyr.svg)](https://badge.fury.io/js/plyr) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/sampotts/plyr) [![Financial Contributors on Open Collective](https://opencollective.com/plyr/all/badge.svg?label=financial+contributors)](https://opencollective.com/plyr) +[![npm version](https://badge.fury.io/js/plyr.svg)](https://badge.fury.io/js/plyr) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/sampotts/plyr) [![Financial Contributors on Open Collective](https://opencollective.com/plyr/all/badge.svg?label=financial+contributors)](https://opencollective.com/plyr) [![Image of Plyr](https://cdn.plyr.io/static/demo/screenshot.png?v=3)](https://plyr.io) @@ -448,7 +448,7 @@ player.source = { ], poster: '/path/to/poster.jpg', previewThumbnails: { - src: '/path/to/thumbnails.vtt' + src: '/path/to/thumbnails.vtt', }, tracks: [ { @@ -579,6 +579,9 @@ player.on('ready', event => { | `waiting` | Sent when the requested operation (such as playback) is delayed pending the completion of another operation (such as a seek). | | `emptied` | he media has become empty; for example, this event is sent if the media has already been loaded (or partially loaded), and the `load()` method is called to reload it. | | `cuechange` | Sent when a `TextTrack` has changed the currently displaying cues. | +| `entertrim` | Sent when the player enters the trimming tool | +| `exittrim` | Sent when the player exits the trimming tool mode. | +| `trimchange` | Sent when the trimming region has changed. | | `error` | Sent when an error occurs. The element's `error` attribute contains more information. | ### YouTube only @@ -620,6 +623,7 @@ document then the shortcuts will work when any element has focus, apart from an | `F` | Toggle fullscreen | | `C` | Toggle captions | | `L` | Toggle loop | +| `T` | Toggle trimming tool | # Preview thumbnails @@ -631,6 +635,10 @@ You can see the example VTT files [here](https://cdn.plyr.io/static/demo/thumbs/ Fullscreen in Plyr is supported by all browsers that [currently support it](http://caniuse.com/#feat=fullscreen). +# Trimming + +It's possible to create a trim region for your video using the trim control. You will need to generate the clip yourself through listening to the 'trimchange' event and will return the start and end of the selected region. + # Browser support Plyr supports the last 2 versions of most _modern_ browsers. @@ -777,7 +785,6 @@ Support this project with your organization. Your logo will show up here with a - # Copyright and License [The MIT license](license.md) diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js index c299a3c9f..1b0bc1e6c 100644 --- a/src/js/config/defaults.js +++ b/src/js/config/defaults.js @@ -110,6 +110,11 @@ const defaults = { update: false, }, + // Trim settings + trim: { + enabled: true, // Allow trim? + }, + // Fullscreen settings fullscreen: { enabled: true, // Allow fullscreen? @@ -140,6 +145,7 @@ const defaults = { 'pip', 'airplay', // 'download', + 'trim', 'fullscreen', ], settings: ['captions', 'quality', 'speed'], @@ -163,6 +169,8 @@ const defaults = { enableCaptions: 'Enable captions', disableCaptions: 'Disable captions', download: 'Download', + enterTrim: 'Enter trim', + exitTrim: 'Exit trim', enterFullscreen: 'Enter fullscreen', exitFullscreen: 'Exit fullscreen', frameTitle: 'Player for {title}', @@ -220,6 +228,7 @@ const defaults = { volume: null, captions: null, download: null, + trim: null, fullscreen: null, pip: null, airplay: null, @@ -281,6 +290,11 @@ const defaults = { 'adsallcomplete', 'adsimpression', 'adsclick', + + // Trimming + 'entertrim', + 'exittrim', + 'trimchange', ], // Selectors @@ -302,6 +316,7 @@ const defaults = { mute: '[data-plyr="mute"]', captions: '[data-plyr="captions"]', download: '[data-plyr="download"]', + trim: '[data-plyr="trim"]', fullscreen: '[data-plyr="fullscreen"]', pip: '[data-plyr="pip"]', airplay: '[data-plyr="airplay"]', @@ -365,6 +380,14 @@ const defaults = { enabled: 'plyr--captions-enabled', active: 'plyr--captions-active', }, + trim: { + enabled: 'plyr--trim-enabled', + active: 'plyr--trim-active', + // Trim tool + trimTool: 'plyr__trim-tool', + leftThumb: 'plyr__trim-thumb-left', + rightThumb: 'plyr__trim-thumb-right', + }, fullscreen: { enabled: 'plyr--fullscreen-enabled', fallback: 'plyr--fullscreen-fallback', diff --git a/src/js/controls.js b/src/js/controls.js index 66ec71398..0875987b6 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -53,7 +53,7 @@ const controls = { // Buttons this.elements.buttons = { - play: getElements.call(this, this.config.selectors.buttons.play), + play: getElements.call(this, this.config.selectors.buttons), pause: getElement.call(this, this.config.selectors.buttons.pause), restart: getElement.call(this, this.config.selectors.buttons.restart), rewind: getElement.call(this, this.config.selectors.buttons.rewind), @@ -63,6 +63,7 @@ const controls = { airplay: getElement.call(this, this.config.selectors.buttons.airplay), settings: getElement.call(this, this.config.selectors.buttons.settings), captions: getElement.call(this, this.config.selectors.buttons.captions), + trim: getElement.call(this, this.config.selectors.buttons.trim), fullscreen: getElement.call(this, this.config.selectors.buttons.fullscreen), }; @@ -230,6 +231,14 @@ const controls = { props.iconPressed = 'captions-on'; break; + case 'trim': + props.toggle = true; + props.label = 'enterTrim'; + props.labelPressed = 'exitTrim'; + props.icon = 'enter-trim'; + props.iconPressed = 'exit-trim'; + break; + case 'fullscreen': props.toggle = true; props.label = 'enterFullscreen'; @@ -1592,6 +1601,11 @@ const controls = { container.appendChild(createButton.call(this, 'download', attributes)); } + // Toggle trim button + if (control === 'trim') { + container.appendChild(createButton.call(this, 'trim', defaultAttributes)); + } + // Toggle fullscreen button if (control === 'fullscreen') { container.appendChild(createButton.call(this, 'fullscreen', defaultAttributes)); diff --git a/src/js/listeners.js b/src/js/listeners.js index 6a0046eeb..34a5095c9 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -130,6 +130,11 @@ class Listeners { player.rewind(); break; + case 84: + // T key + player.trim.toggle(); + break; + case 70: // F key player.fullscreen.toggle(); @@ -575,6 +580,16 @@ class Listeners { 'download', ); + // Trim toggle + this.bind( + elements.buttons.trim, + 'click', + () => { + player.trim.toggle(); + }, + 'trim', + ); + // Fullscreen toggle this.bind( elements.buttons.fullscreen, @@ -610,7 +625,7 @@ class Listeners { controls.toggleMenu.call(player, event); }, null, - false + false, ); // Can't be passive as we're preventing default // Settings menu - keyboard toggle @@ -757,6 +772,24 @@ class Listeners { } }); + // Move trim handles if selected + this.bind(elements.controls, 'mousemove touchmove', event => { + const { trim } = player; + + if (trim && trim.tool.editing) { + trim.setTrimLength(event); + } + }); + + // Stop trimming when handle is no longer selected + this.bind(elements.controls, 'mouseup touchend', event => { + const { trim } = player; + + if (trim && trim.tool.editing) { + trim.setEditing(event); + } + }); + // Polyfill for lower fill in for webkit if (browser.isWebkit) { Array.from(getElements.call(player, 'input[type="range"]')).forEach(element => { diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js new file mode 100644 index 000000000..9db19ae8b --- /dev/null +++ b/src/js/plugins/trim.js @@ -0,0 +1,229 @@ +// ========================================================================== +// Plyr Trim control +// ========================================================================== + +import { createElement, toggleClass, toggleHidden } from '../utils/elements'; +import { on, triggerEvent } from '../utils/events'; +import is from '../utils/is'; + +class Trim { + constructor(player) { + // Keep reference to parent + this.player = player; + this.config = player.config.trim; + this.loaded = false; + this.trimming = false; + this.trimLength = 30; + this.tool = { + bar: null, + leftThumb: null, + rightThumb: null, + editing: null, + }; + + // Handle event (incase user presses escape etc) + on.call(this.player, document, () => { + this.onChange(); + }); + + // Update the UI + this.update(); + + // Prevent the trim tool from being added until the player is in a playable state + // If the user has pressed the trim tool before this event has fired, show the tool + this.player.once('canplay', () => { + this.loaded = true; + if (this.trimming) { + this.createTrimTool(); + } + }); + } + + // Determine if trim is enabled + get enabled() { + const { config } = this; + return config.enabled && this.player.isHTML5 && this.player.isVideo; + } + + // Get active state + get active() { + if (!this.enabled) { + return false; + } + + return this.trimming; + } + + // Get trim range in seconds + getTrimRange() { + const { left, width } = this.tool.bar.style; + const start = this.player.media.duration * (parseFloat(left) / 100); + const end = this.player.media.duration * ((parseFloat(left) + parseFloat(width)) / 100); + + this.player.debug.log(`Set trimming range from ${start}seconds to ${end}seconds`); + return { start, end }; + } + + // Show the trim toolbar from the timeline + showTrimTool() { + if (!this.tool.bar) { + this.createTrimTool(); + } + toggleHidden(this.tool.bar, false); + } + + // Hide the trim toolbar from the timeline + hideTrimTool() { + toggleHidden(this.tool.bar, true); + } + + // Add trim toolbar to the timeline + createTrimTool() { + const seekElement = this.player.elements.progress; + if (is.element(seekElement) && this.loaded) { + this.createTrimBar(seekElement); + this.createTrimBarThumbs(); + } + } + + // Add trim bar to the timeline + createTrimBar(seekElement) { + // Set the trim bar from the current seek time to the trimlength + const start = this.player.elements.inputs.seek.value; + // Set the trim bar to be x number of seconds after start and prevent from overflowing + const end = Math.min( + parseFloat(start) + (100 / this.player.duration) * this.trimLength, + 100 - parseFloat(start), + ); + this.tool.bar = createElement('span', { + class: this.player.config.classNames.trim.trimTool, + }); + + this.tool.bar.style.left = `${start.toString()}%`; + this.tool.bar.style.width = `${end.toString()}%`; + seekElement.appendChild(this.tool.bar); + + triggerEvent.call(this.player, this.getTrimRange(), 'trimchange'); + } + + // Add trim length thumbs to the timeline + createTrimBarThumbs() { + const { trim } = this.player.config.classNames; + + // Create the trim bar thumb elements + this.tool.leftThumb = createElement('span', { class: trim.leftThumb }); + this.tool.rightThumb = createElement('span', { class: trim.rightThumb }); + + // Add the thumbs to the bar + this.tool.bar.appendChild(this.tool.leftThumb); + this.tool.bar.appendChild(this.tool.rightThumb); + + // Add listens for trim thumb (handle) selection + this.player.listeners.bind(this.tool.leftThumb, 'mousedown touchstart', event => { + if (this.tool.bar) { + this.setEditing(event); + } + }); + + // Listen for trim thumb (handle) selection + this.player.listeners.bind(this.tool.rightThumb, 'mousedown touchstart', event => { + if (this.tool.bar) { + this.setEditing(event); + } + }); + } + + setEditing(event) { + const { leftThumb, rightThumb } = this.player.config.classNames.trim; + const { type, target } = event; + + if (type === 'mouseup' || type === 'touchend') { + this.tool.editing = null; + triggerEvent.call(this.player, this.getTrimRange(), 'trimchange'); + } else if ((type === 'mousedown' || type === 'touchstart') && target.classList.contains(leftThumb)) { + this.tool.editing = leftThumb; + } else if ((type === 'mousedown' || type === 'touchstart') && target.classList.contains(rightThumb)) { + this.tool.editing = rightThumb; + } + } + + setTrimLength(event) { + if (!this.tool.editing) return; + + const { leftThumb, rightThumb } = this.player.config.classNames.trim; + const { bar, editing } = this.tool; + const clientRect = this.player.elements.progress.getBoundingClientRect(); + const xPos = event.type === 'touchmove' ? event.touches[0].pageX : event.pageX; + // Percentage must be between 0 and 100 + const percentage = Math.max(Math.min((100 / clientRect.width) * (xPos - clientRect.left), 100), 0); + + /* Alter width of the trim region + - If left thumb selected increase width and keep right hand side in same position + - If right thumb selected just decrease the width */ + if (editing === leftThumb) { + bar.style.width = `${parseFloat(bar.style.width) - (percentage - parseFloat(bar.style.left))}%`; + bar.style.left = `${percentage}%`; + // Update seek position to match the left thumbs position if less than the current left thumb position + } else if (editing === rightThumb) { + bar.style.width = `${percentage - parseFloat(bar.style.left)}%`; + } + } + + // On toggle of trim control, trigger event + onChange() { + if (!this.enabled) { + return; + } + + // Update toggle button + const button = this.player.elements.buttons.trim; + if (is.element(button)) { + button.pressed = this.active; + } + + // Trigger an event + triggerEvent.call(this.player, this.media, this.active ? 'entertrim' : 'exittrim', true); + } + + // Update UI + update() { + if (this.enabled) { + this.player.debug.log(`trim enabled`); + } else { + this.player.debug.log('Trimming is not supported'); + } + + // Add styling hook to show button + toggleClass(this.player.elements.container, this.player.config.classNames.trim.enabled, this.enabled); + } + + // Enter trim tool + enter() { + if (!this.enabled) { + return; + } + this.trimming = true; + this.showTrimTool(); + } + + // Exit trim tool + exit() { + if (!this.enabled) { + return; + } + this.trimming = false; + this.hideTrimTool(); + } + + // Toggle state + toggle() { + if (!this.active) { + this.enter(); + } else { + this.exit(); + } + this.onChange(); + } +} + +export default Trim; diff --git a/src/js/plyr.d.ts b/src/js/plyr.d.ts index 501794684..194ee7ae7 100644 --- a/src/js/plyr.d.ts +++ b/src/js/plyr.d.ts @@ -245,6 +245,8 @@ declare namespace Plyr { | 'seeked' | 'ratechange' | 'ended' + | 'entertrim' + | 'exittrim' | 'enterfullscreen' | 'exitfullscreen' | 'captionsenabled' @@ -435,6 +437,12 @@ declare namespace Plyr { */ captions?: CaptionOptions; + /** + * enabled: Toggles whether fullscreen should be enabled. fallback: Allow fallback to a full-window solution. + * iosNative: whether to use native iOS fullscreen when entering fullscreen (no custom controls) + */ + trim?: TrimOptions; + /** * enabled: Toggles whether fullscreen should be enabled. fallback: Allow fallback to a full-window solution. * iosNative: whether to use native iOS fullscreen when entering fullscreen (no custom controls) @@ -502,6 +510,11 @@ declare namespace Plyr { seek?: boolean; } + interface TrimOptions { + enabled?: boolean; + active?: boolean; + } + interface FullScreenOptions { enabled?: boolean; fallback?: boolean; diff --git a/src/js/plyr.js b/src/js/plyr.js index 00d334631..8391412a0 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -16,6 +16,7 @@ import Listeners from './listeners'; import media from './media'; import Ads from './plugins/ads'; import PreviewThumbnails from './plugins/preview-thumbnails'; +import Trim from './plugins/trim'; import source from './source'; import Storage from './storage'; import support from './support'; @@ -293,6 +294,9 @@ class Plyr { // Global listeners this.listeners.global(); + // Setup trim + this.trim = new Trim(this); + // Setup fullscreen this.fullscreen = new Fullscreen(this); @@ -1023,6 +1027,21 @@ class Plyr { return this.media === document.pictureInPictureElement; } + /** + * Toggle Trim + * @param {Boolean} input - Whether to show trimming tool + */ + set trimming(input) { + this.trim.toggle.call(this, input, false); + } + + /** + * Get the current trim state + */ + get trimming() { + return this.trim.trimming; + } + /** * Trigger the airplay dialog * TODO: update player with state, support, enabled diff --git a/src/sass/components/controls.scss b/src/sass/components/controls.scss index 50333701e..0ebf7f3eb 100644 --- a/src/sass/components/controls.scss +++ b/src/sass/components/controls.scss @@ -53,12 +53,14 @@ .plyr [data-plyr='captions'], .plyr [data-plyr='pip'], .plyr [data-plyr='airplay'], +.plyr [data-plyr='trim'], .plyr [data-plyr='fullscreen'] { display: none; } .plyr--captions-enabled [data-plyr='captions'], .plyr--pip-supported [data-plyr='pip'], .plyr--airplay-supported [data-plyr='airplay'], +.plyr--trim-enabled [data-plyr='trim'], .plyr--fullscreen-enabled [data-plyr='fullscreen'] { display: inline-block; } diff --git a/src/sass/components/trim.scss b/src/sass/components/trim.scss new file mode 100644 index 000000000..6780be739 --- /dev/null +++ b/src/sass/components/trim.scss @@ -0,0 +1,25 @@ +// -------------------------------------------------------------- +// Trim +// -------------------------------------------------------------- + +.plyr__trim { + animation: plyr-fade-in 0.3s ease; + bottom: 0; + color: $plyr-captions-color; + display: none; + font-size: $plyr-font-size-captions-small; + left: 0; + padding: $plyr-control-spacing; + position: absolute; + text-align: center; + transition: transform 0.4s ease-in-out; + width: 100%; + + span:empty { + display: none; + } +} + +.plyr--captions-active .plyr__captions { + display: block; +} diff --git a/src/sass/plugins/trim.scss b/src/sass/plugins/trim.scss new file mode 100644 index 000000000..436dfa6dc --- /dev/null +++ b/src/sass/plugins/trim.scss @@ -0,0 +1,49 @@ +// -------------------------------------------------------------- +// Trim Tool +// -------------------------------------------------------------- + +$plyr-trim-thumb-width: 6px !default; +$plyr-trim-thumb-height: 15px !default; +$plyr-trim-thumb-border: 2px !default; +$plyr-trim-thumb-border-radius: 5px !default; + +// Trim Tool for the progress bar +.plyr__trim-tool { + background: currentColor; + display: block; + height: $plyr-range-track-height; + left: 0; + margin: -($plyr-range-track-height / 2) 0 0; + opacity: 0.8; + position: absolute; + top: $plyr-range-track-height; + width: 3px; + z-index: 4; // Between progress and thumb +} + +.plyr__trim-thumb-left, +.plyr__trim-thumb-right { + background: $plyr-color-main; + border: $plyr-trim-thumb-border; + border-radius: $plyr-trim-thumb-border-radius; + height: $plyr-trim-thumb-height; + position: absolute; + top: -$plyr-range-max-height / 2 + $plyr-trim-thumb-border; + touch-action: none; + user-select: none; + width: $plyr-trim-thumb-width; + z-index: 3; +} + +.plyr__trim-thumb-left { + left: -$plyr-trim-thumb-width; +} + +.plyr__trim-thumb-right { + right: -$plyr-trim-thumb-width; +} + +.plyr__trim-thumb-left:active, +.plyr__trim-thumb-right:active { + @include plyr-range-thumb-active($plyr-color-main); +} diff --git a/src/sass/plyr.scss b/src/sass/plyr.scss index cb8a39210..1432e96a5 100644 --- a/src/sass/plyr.scss +++ b/src/sass/plyr.scss @@ -27,6 +27,7 @@ @import 'components/badges'; @import 'components/captions'; +@import 'components/trim'; @import 'components/control'; @import 'components/controls'; @import 'components/menus'; @@ -44,6 +45,7 @@ @import 'plugins/ads'; @import 'plugins/preview-thumbnails'; +@import 'plugins/trim'; @import 'utils/animation'; @import 'utils/hidden'; From 4c75bccc56cbcf28cf0c3c8cdbf4c219c52e64ca Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Fri, 3 Apr 2020 13:02:54 +0100 Subject: [PATCH 02/20] Trim tool icons added --- src/sprite/plyr-enter-trim.svg | 5 +++++ src/sprite/plyr-exit-trim.svg | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 src/sprite/plyr-enter-trim.svg create mode 100644 src/sprite/plyr-exit-trim.svg diff --git a/src/sprite/plyr-enter-trim.svg b/src/sprite/plyr-enter-trim.svg new file mode 100644 index 000000000..f7947344a --- /dev/null +++ b/src/sprite/plyr-enter-trim.svg @@ -0,0 +1,5 @@ + +image/svg+xml + + + \ No newline at end of file diff --git a/src/sprite/plyr-exit-trim.svg b/src/sprite/plyr-exit-trim.svg new file mode 100644 index 000000000..bdb0d2cd8 --- /dev/null +++ b/src/sprite/plyr-exit-trim.svg @@ -0,0 +1,5 @@ + +image/svg+xml + + + From c82fa7352cab6276d170d2437bb63d61113a59a2 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Fri, 3 Apr 2020 16:56:45 +0100 Subject: [PATCH 03/20] If the user is using the trimming tool, check if the seek position is outside of the trimming region and set to the start of the trimming region --- src/js/plugins/trim.js | 86 ++++++++++++++++++++++++++++-------------- src/js/plyr.d.ts | 9 ++++- 2 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index 9db19ae8b..aca5ca9c5 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -13,7 +13,9 @@ class Trim { this.config = player.config.trim; this.loaded = false; this.trimming = false; - this.trimLength = 30; + this.defaultTrimLength = 30; + this.startTime = 0; + this.endTime = 0; this.tool = { bar: null, leftThumb: null, @@ -29,14 +31,7 @@ class Trim { // Update the UI this.update(); - // Prevent the trim tool from being added until the player is in a playable state - // If the user has pressed the trim tool before this event has fired, show the tool - this.player.once('canplay', () => { - this.loaded = true; - if (this.trimming) { - this.createTrimTool(); - } - }); + this.listeners(); } // Determine if trim is enabled @@ -54,14 +49,18 @@ class Trim { return this.trimming; } - // Get trim range in seconds - getTrimRange() { - const { left, width } = this.tool.bar.style; - const start = this.player.media.duration * (parseFloat(left) / 100); - const end = this.player.media.duration * ((parseFloat(left) + parseFloat(width)) / 100); + get trimTime() { + return { startTime: this.startTime, endTime: this.endTime }; + } - this.player.debug.log(`Set trimming range from ${start}seconds to ${end}seconds`); - return { start, end }; + // Store the trim start time in seconds + setStartTime(percentage) { + this.startTime = this.player.media.duration * (parseFloat(percentage) / 100); + } + + // Store the trim end time in seconds + setEndTime(percentage) { + this.endTime = this.startTime + this.player.media.duration * (parseFloat(percentage) / 100); } // Show the trim toolbar from the timeline @@ -88,13 +87,14 @@ class Trim { // Add trim bar to the timeline createTrimBar(seekElement) { - // Set the trim bar from the current seek time to the trimlength + // Set the trim bar from the current seek time percentage to x number of seconds after and limit the end percentage to 100% const start = this.player.elements.inputs.seek.value; - // Set the trim bar to be x number of seconds after start and prevent from overflowing - const end = Math.min( - parseFloat(start) + (100 / this.player.duration) * this.trimLength, - 100 - parseFloat(start), - ); + const end = Math.min(parseFloat(start) + (100 / this.player.duration) * this.defaultTrimLength, 100 - start); + + // Store the start and end video percentages in seconds + this.setStartTime(start); + this.setEndTime(end); + this.tool.bar = createElement('span', { class: this.player.config.classNames.trim.trimTool, }); @@ -103,7 +103,7 @@ class Trim { this.tool.bar.style.width = `${end.toString()}%`; seekElement.appendChild(this.tool.bar); - triggerEvent.call(this.player, this.getTrimRange(), 'trimchange'); + triggerEvent.call(this.player, this.trimTime, 'trimchange'); } // Add trim length thumbs to the timeline @@ -139,7 +139,7 @@ class Trim { if (type === 'mouseup' || type === 'touchend') { this.tool.editing = null; - triggerEvent.call(this.player, this.getTrimRange(), 'trimchange'); + triggerEvent.call(this.player, this.trimTime, 'trimchange'); } else if ((type === 'mousedown' || type === 'touchstart') && target.classList.contains(leftThumb)) { this.tool.editing = leftThumb; } else if ((type === 'mousedown' || type === 'touchstart') && target.classList.contains(rightThumb)) { @@ -150,25 +150,55 @@ class Trim { setTrimLength(event) { if (!this.tool.editing) return; - const { leftThumb, rightThumb } = this.player.config.classNames.trim; - const { bar, editing } = this.tool; const clientRect = this.player.elements.progress.getBoundingClientRect(); + // Mouse Position const xPos = event.type === 'touchmove' ? event.touches[0].pageX : event.pageX; // Percentage must be between 0 and 100 const percentage = Math.max(Math.min((100 / clientRect.width) * (xPos - clientRect.left), 100), 0); - /* Alter width of the trim region - If left thumb selected increase width and keep right hand side in same position - If right thumb selected just decrease the width */ + const { leftThumb, rightThumb } = this.player.config.classNames.trim; + const { bar, editing } = this.tool; if (editing === leftThumb) { bar.style.width = `${parseFloat(bar.style.width) - (percentage - parseFloat(bar.style.left))}%`; bar.style.left = `${percentage}%`; + this.setStartTime(percentage); // Update seek position to match the left thumbs position if less than the current left thumb position } else if (editing === rightThumb) { - bar.style.width = `${percentage - parseFloat(bar.style.left)}%`; + const end = percentage - parseFloat(bar.style.left); + bar.style.width = `${end}%`; + this.setEndTime(end); } } + listeners() { + /* Prevent the trim tool from being added until the player is in a playable state + If the user has pressed the trim tool before this event has fired, show the tool */ + this.player.once('canplay', () => { + this.loaded = true; + if (this.trimming) { + this.createTrimTool(); + } + }); + + // Set the seektime to the start of the trim timeline, if the seektime is outside of the region. + this.player.on('timeupdate', () => { + if (!(this.active && this.trimming && this.player.playing)) { + return; + } + + const { currentTime } = this.player; + if (currentTime < this.startTime || currentTime >= this.endTime) { + this.player.currentTime = this.startTime; + + if (currentTime >= this.endTime) { + this.player.pause(); + } + } + }); + } + // On toggle of trim control, trigger event onChange() { if (!this.enabled) { diff --git a/src/js/plyr.d.ts b/src/js/plyr.d.ts index c16ba1273..9eeecc109 100644 --- a/src/js/plyr.d.ts +++ b/src/js/plyr.d.ts @@ -453,8 +453,7 @@ declare namespace Plyr { captions?: CaptionOptions; /** - * enabled: Toggles whether fullscreen should be enabled. fallback: Allow fallback to a full-window solution. - * iosNative: whether to use native iOS fullscreen when entering fullscreen (no custom controls) + * enabled: Toggles whether trimming should be enabled. */ trim?: TrimOptions; @@ -538,6 +537,12 @@ declare namespace Plyr { interface TrimOptions { enabled?: boolean; active?: boolean; + trimTime?: TrimTime; + } + + interface TrimTime { + startTime: number; + endTime: number; } interface FullScreenOptions { From f5bc7295e546142db2289b2f09a2173caa7a059a Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Sat, 4 Apr 2020 20:23:15 +0100 Subject: [PATCH 04/20] Improved styling of Trimming timeline --- src/sass/plugins/trim.scss | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/sass/plugins/trim.scss b/src/sass/plugins/trim.scss index 436dfa6dc..ba100fc84 100644 --- a/src/sass/plugins/trim.scss +++ b/src/sass/plugins/trim.scss @@ -2,10 +2,11 @@ // Trim Tool // -------------------------------------------------------------- -$plyr-trim-thumb-width: 6px !default; -$plyr-trim-thumb-height: 15px !default; +$plyr-trim-thumb-width: 7px !default; +$plyr-trim-thumb-height: $plyr-range-max-height !default; $plyr-trim-thumb-border: 2px !default; $plyr-trim-thumb-border-radius: 5px !default; +$plyr-trim-box-shadow: inset 0 0 0 0.5px darken($plyr-color-main, 5%) !default; // Trim Tool for the progress bar .plyr__trim-tool { @@ -13,12 +14,11 @@ $plyr-trim-thumb-border-radius: 5px !default; display: block; height: $plyr-range-track-height; left: 0; - margin: -($plyr-range-track-height / 2) 0 0; - opacity: 0.8; + margin-top: -($plyr-range-track-height / 2); position: absolute; top: $plyr-range-track-height; + top: 50%; width: 3px; - z-index: 4; // Between progress and thumb } .plyr__trim-thumb-left, @@ -26,9 +26,10 @@ $plyr-trim-thumb-border-radius: 5px !default; background: $plyr-color-main; border: $plyr-trim-thumb-border; border-radius: $plyr-trim-thumb-border-radius; + box-shadow: $plyr-trim-box-shadow; height: $plyr-trim-thumb-height; position: absolute; - top: -$plyr-range-max-height / 2 + $plyr-trim-thumb-border; + top: -$plyr-trim-thumb-height / 2 + $plyr-trim-thumb-border; touch-action: none; user-select: none; width: $plyr-trim-thumb-width; From 13d15b67bc3c1665fb46271b32efd5e23094fe46 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Sat, 4 Apr 2020 20:25:44 +0100 Subject: [PATCH 05/20] Fix to only update the current time when the user has finished setting the trimming length (let go of trimming handle) --- dist/plyr.svg | 2 +- src/js/plugins/trim.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/plyr.svg b/dist/plyr.svg index 62ab2579b..d16084d7a 100644 --- a/dist/plyr.svg +++ b/dist/plyr.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index aca5ca9c5..fa1eeb6ad 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -184,7 +184,7 @@ class Trim { // Set the seektime to the start of the trim timeline, if the seektime is outside of the region. this.player.on('timeupdate', () => { - if (!(this.active && this.trimming && this.player.playing)) { + if (!this.active || !this.trimming || !this.player.playing || this.tool.editing) { return; } From ef5bccfd7606cc8b7e343ac92c3ac7cb84961b75 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Sun, 5 Apr 2020 19:26:03 +0100 Subject: [PATCH 06/20] Trimming tool: Implementation of timestamp above each thumb to display the seek position in the video | Trim plugin is also brought to be more inline with the existing code base (sass structure and change of this.tool to be this.elements) --- src/js/config/defaults.js | 6 +- src/js/listeners.js | 4 +- src/js/plugins/trim.js | 121 +++++++++++++++++++++++++------------ src/sass/plugins/trim.scss | 76 +++++++++++++++-------- 4 files changed, 140 insertions(+), 67 deletions(-) diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js index 1b0bc1e6c..b4b14f137 100644 --- a/src/js/config/defaults.js +++ b/src/js/config/defaults.js @@ -385,8 +385,10 @@ const defaults = { active: 'plyr--trim-active', // Trim tool trimTool: 'plyr__trim-tool', - leftThumb: 'plyr__trim-thumb-left', - rightThumb: 'plyr__trim-thumb-right', + leftThumb: 'plyr__trim-tool__thumb-left', + rightThumb: 'plyr__trim-tool__thumb-right', + timeContainer: 'plyr__trim-tool__time-container', + timeContainerShown: 'plyr__trim-tool__time-container--is-shown', }, fullscreen: { enabled: 'plyr--fullscreen-enabled', diff --git a/src/js/listeners.js b/src/js/listeners.js index 34a5095c9..5180e7a01 100644 --- a/src/js/listeners.js +++ b/src/js/listeners.js @@ -776,7 +776,7 @@ class Listeners { this.bind(elements.controls, 'mousemove touchmove', event => { const { trim } = player; - if (trim && trim.tool.editing) { + if (trim && trim.editing) { trim.setTrimLength(event); } }); @@ -785,7 +785,7 @@ class Listeners { this.bind(elements.controls, 'mouseup touchend', event => { const { trim } = player; - if (trim && trim.tool.editing) { + if (trim && trim.editing) { trim.setEditing(event); } }); diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index fa1eeb6ad..23ebf073f 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -5,6 +5,7 @@ import { createElement, toggleClass, toggleHidden } from '../utils/elements'; import { on, triggerEvent } from '../utils/events'; import is from '../utils/is'; +import { formatTime } from '../utils/time'; class Trim { constructor(player) { @@ -13,25 +14,15 @@ class Trim { this.config = player.config.trim; this.loaded = false; this.trimming = false; + this.editing = false; this.defaultTrimLength = 30; this.startTime = 0; this.endTime = 0; - this.tool = { - bar: null, - leftThumb: null, - rightThumb: null, - editing: null, + this.elements = { + bar: {}, }; - // Handle event (incase user presses escape etc) - on.call(this.player, document, () => { - this.onChange(); - }); - - // Update the UI - this.update(); - - this.listeners(); + this.load(); } // Determine if trim is enabled @@ -49,10 +40,24 @@ class Trim { return this.trimming; } + // Get the current trim time get trimTime() { return { startTime: this.startTime, endTime: this.endTime }; } + load() { + // Handle event (incase user presses escape etc) + on.call(this.player, document, () => { + this.onChange(); + }); + + // Update the UI + this.update(); + + // Setup player listeners + this.listeners(); + } + // Store the trim start time in seconds setStartTime(percentage) { this.startTime = this.player.media.duration * (parseFloat(percentage) / 100); @@ -63,17 +68,17 @@ class Trim { this.endTime = this.startTime + this.player.media.duration * (parseFloat(percentage) / 100); } - // Show the trim toolbar from the timeline + // Show the trim toolbar on the timeline showTrimTool() { - if (!this.tool.bar) { + if (is.empty(this.elements.bar)) { this.createTrimTool(); } - toggleHidden(this.tool.bar, false); + toggleHidden(this.elements.bar, false); } // Hide the trim toolbar from the timeline hideTrimTool() { - toggleHidden(this.tool.bar, true); + toggleHidden(this.elements.bar, true); } // Add trim toolbar to the timeline @@ -82,6 +87,7 @@ class Trim { if (is.element(seekElement) && this.loaded) { this.createTrimBar(seekElement); this.createTrimBarThumbs(); + this.createThumbTime(); } } @@ -95,13 +101,13 @@ class Trim { this.setStartTime(start); this.setEndTime(end); - this.tool.bar = createElement('span', { + this.elements.bar = createElement('span', { class: this.player.config.classNames.trim.trimTool, }); - this.tool.bar.style.left = `${start.toString()}%`; - this.tool.bar.style.width = `${end.toString()}%`; - seekElement.appendChild(this.tool.bar); + this.elements.bar.style.left = `${start.toString()}%`; + this.elements.bar.style.width = `${end.toString()}%`; + seekElement.appendChild(this.elements.bar); triggerEvent.call(this.player, this.trimTime, 'trimchange'); } @@ -111,44 +117,72 @@ class Trim { const { trim } = this.player.config.classNames; // Create the trim bar thumb elements - this.tool.leftThumb = createElement('span', { class: trim.leftThumb }); - this.tool.rightThumb = createElement('span', { class: trim.rightThumb }); + this.elements.bar.leftThumb = createElement('span', { class: trim.leftThumb }); + this.elements.bar.rightThumb = createElement('span', { class: trim.rightThumb }); // Add the thumbs to the bar - this.tool.bar.appendChild(this.tool.leftThumb); - this.tool.bar.appendChild(this.tool.rightThumb); + this.elements.bar.appendChild(this.elements.bar.leftThumb); + this.elements.bar.appendChild(this.elements.bar.rightThumb); // Add listens for trim thumb (handle) selection - this.player.listeners.bind(this.tool.leftThumb, 'mousedown touchstart', event => { - if (this.tool.bar) { + this.player.listeners.bind(this.elements.bar.leftThumb, 'mousedown touchstart', event => { + if (this.elements.bar) { this.setEditing(event); } }); // Listen for trim thumb (handle) selection - this.player.listeners.bind(this.tool.rightThumb, 'mousedown touchstart', event => { - if (this.tool.bar) { + this.player.listeners.bind(this.elements.bar.rightThumb, 'mousedown touchstart', event => { + if (this.elements.bar) { this.setEditing(event); } }); } + createThumbTime() { + // Create HTML element, parent+span: time text (e.g., 01:32:00) + this.elements.bar.leftThumb.timeContainer = createElement('div', { + class: this.player.config.classNames.trim.timeContainer, + }); + + this.elements.bar.rightThumb.timeContainer = createElement('div', { + class: this.player.config.classNames.trim.timeContainer, + }); + + // Append the time element to the container + this.elements.bar.leftThumb.timeContainer.time = createElement('span', {}, formatTime(this.startTime)); + this.elements.bar.leftThumb.timeContainer.appendChild(this.elements.bar.leftThumb.timeContainer.time); + this.elements.bar.rightThumb.timeContainer.time = createElement('span', {}, formatTime(this.endTime)); + this.elements.bar.rightThumb.timeContainer.appendChild(this.elements.bar.rightThumb.timeContainer.time); + + // Append the time container to the bar + this.elements.bar.leftThumb.appendChild(this.elements.bar.leftThumb.timeContainer); + this.elements.bar.rightThumb.appendChild(this.elements.bar.rightThumb.timeContainer); + } + setEditing(event) { const { leftThumb, rightThumb } = this.player.config.classNames.trim; const { type, target } = event; - if (type === 'mouseup' || type === 'touchend') { - this.tool.editing = null; + if ((type === 'mouseup' || type === 'touchend') && this.editing === leftThumb) { + this.editing = null; + this.toggleTimeContainer(this.elements.bar.leftThumb, false); + triggerEvent.call(this.player, this.trimTime, 'trimchange'); + } else if ((type === 'mouseup' || type === 'touchend') && this.editing === rightThumb) { + this.editing = null; + this.toggleTimeContainer(this.elements.bar.rightThumb, false); triggerEvent.call(this.player, this.trimTime, 'trimchange'); } else if ((type === 'mousedown' || type === 'touchstart') && target.classList.contains(leftThumb)) { - this.tool.editing = leftThumb; + this.editing = leftThumb; + this.toggleTimeContainer(this.elements.bar.leftThumb, true); } else if ((type === 'mousedown' || type === 'touchstart') && target.classList.contains(rightThumb)) { - this.tool.editing = rightThumb; + this.editing = rightThumb; + this.toggleTimeContainer(this.elements.bar.rightThumb, true); } } setTrimLength(event) { - if (!this.tool.editing) return; + if (!this.editing) return; const clientRect = this.player.elements.progress.getBoundingClientRect(); // Mouse Position @@ -159,19 +193,28 @@ class Trim { - If left thumb selected increase width and keep right hand side in same position - If right thumb selected just decrease the width */ const { leftThumb, rightThumb } = this.player.config.classNames.trim; - const { bar, editing } = this.tool; - if (editing === leftThumb) { + const { bar } = this.elements; + if (this.editing === leftThumb) { bar.style.width = `${parseFloat(bar.style.width) - (percentage - parseFloat(bar.style.left))}%`; bar.style.left = `${percentage}%`; this.setStartTime(percentage); + // Set the timestamp of the current trim handle position + bar.leftThumb.timeContainer.time.innerText = formatTime(this.startTime); // Update seek position to match the left thumbs position if less than the current left thumb position - } else if (editing === rightThumb) { + } else if (this.editing === rightThumb) { const end = percentage - parseFloat(bar.style.left); bar.style.width = `${end}%`; this.setEndTime(end); + // Set the timestamp of the current trim handle position + bar.rightThumb.timeContainer.time.innerText = formatTime(this.endTime); } } + toggleTimeContainer(element, toggle = false) { + const className = this.player.config.classNames.trim.timeContainerShown; + element.timeContainer.classList.toggle(className, toggle); + } + listeners() { /* Prevent the trim tool from being added until the player is in a playable state If the user has pressed the trim tool before this event has fired, show the tool */ @@ -184,7 +227,7 @@ class Trim { // Set the seektime to the start of the trim timeline, if the seektime is outside of the region. this.player.on('timeupdate', () => { - if (!this.active || !this.trimming || !this.player.playing || this.tool.editing) { + if (!this.active || !this.trimming || !this.player.playing || this.editing) { return; } diff --git a/src/sass/plugins/trim.scss b/src/sass/plugins/trim.scss index ba100fc84..e84cab66e 100644 --- a/src/sass/plugins/trim.scss +++ b/src/sass/plugins/trim.scss @@ -7,6 +7,12 @@ $plyr-trim-thumb-height: $plyr-range-max-height !default; $plyr-trim-thumb-border: 2px !default; $plyr-trim-thumb-border-radius: 5px !default; $plyr-trim-box-shadow: inset 0 0 0 0.5px darken($plyr-color-main, 5%) !default; +$plyr-trim-time-bg: rgba(0, 0, 0, 0.55); +$plyr-trim-time-radius: $plyr-tooltip-radius !default; +$plyr-trim-time-color: #fff; +$plyr-trim-time-font-size: $plyr-font-size-time !default; +$plyr-trim-time-padding: 3px 6px !default; +$plyr-trim-time-bottom-offset: 1px !default; // Trim Tool for the progress bar .plyr__trim-tool { @@ -19,32 +25,54 @@ $plyr-trim-box-shadow: inset 0 0 0 0.5px darken($plyr-color-main, 5%) !default; top: $plyr-range-track-height; top: 50%; width: 3px; -} -.plyr__trim-thumb-left, -.plyr__trim-thumb-right { - background: $plyr-color-main; - border: $plyr-trim-thumb-border; - border-radius: $plyr-trim-thumb-border-radius; - box-shadow: $plyr-trim-box-shadow; - height: $plyr-trim-thumb-height; - position: absolute; - top: -$plyr-trim-thumb-height / 2 + $plyr-trim-thumb-border; - touch-action: none; - user-select: none; - width: $plyr-trim-thumb-width; - z-index: 3; -} + &__thumb-left { + left: -$plyr-trim-thumb-width; + } -.plyr__trim-thumb-left { - left: -$plyr-trim-thumb-width; -} + &__thumb-right { + right: -$plyr-trim-thumb-width; + } -.plyr__trim-thumb-right { - right: -$plyr-trim-thumb-width; -} + &__thumb-left, + &__thumb-right { + background: $plyr-color-main; + border: $plyr-trim-thumb-border; + border-radius: $plyr-trim-thumb-border-radius; + box-shadow: $plyr-trim-box-shadow; + height: $plyr-trim-thumb-height; + position: absolute; + top: -$plyr-trim-thumb-height / 2 + $plyr-trim-thumb-border; + touch-action: none; + user-select: none; + width: $plyr-trim-thumb-width; + z-index: 3; + + &:active { + @include plyr-range-thumb-active($plyr-color-main); + } + } + + &__time-container { + bottom: $plyr-trim-thumb-height + $plyr-trim-time-bottom-offset; + left: 50%; + opacity: 0; + position: absolute; + transform: translate(-50%, -50%); + transition: opacity 0.3s ease; + white-space: nowrap; + z-index: 3; + + &--is-shown { + opacity: 1; + } -.plyr__trim-thumb-left:active, -.plyr__trim-thumb-right:active { - @include plyr-range-thumb-active($plyr-color-main); + span { + background-color: $plyr-trim-time-bg; + border-radius: ($plyr-trim-time-radius - 1px); + color: $plyr-trim-time-color; + font-size: $plyr-trim-time-font-size; + padding: $plyr-trim-time-padding; + } + } } From d8ae6e26ad3dc9678313828dce274e764df1a1b1 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Mon, 6 Apr 2020 11:25:40 +0100 Subject: [PATCH 07/20] Improve accessiblity: Inclusion of aria metadata --- controls.md | 2 ++ src/js/config/defaults.js | 2 ++ src/js/plugins/trim.js | 36 ++++++++++++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/controls.md b/controls.md index d5a09044b..fb45a948b 100644 --- a/controls.md +++ b/controls.md @@ -59,6 +59,8 @@ i18n: { disableCaptions: 'Disable captions', enterTrim: 'Enter trim', exitTrim: 'Exit trim', + trimStart: 'Trim Start', + trimEnd: 'Trim End', enterFullscreen: 'Enter fullscreen', exitFullscreen: 'Exit fullscreen', frameTitle: 'Player for {title}', diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js index b4b14f137..a601109e3 100644 --- a/src/js/config/defaults.js +++ b/src/js/config/defaults.js @@ -171,6 +171,8 @@ const defaults = { download: 'Download', enterTrim: 'Enter trim', exitTrim: 'Exit trim', + trimStart: 'Trim Start', + trimEnd: 'Trim End', enterFullscreen: 'Enter fullscreen', exitFullscreen: 'Exit fullscreen', frameTitle: 'Player for {title}', diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index 23ebf073f..b64e8cab6 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -4,7 +4,9 @@ import { createElement, toggleClass, toggleHidden } from '../utils/elements'; import { on, triggerEvent } from '../utils/events'; +import i18n from '../utils/i18n'; import is from '../utils/is'; +import { extend } from '../utils/objects'; import { formatTime } from '../utils/time'; class Trim { @@ -117,8 +119,32 @@ class Trim { const { trim } = this.player.config.classNames; // Create the trim bar thumb elements - this.elements.bar.leftThumb = createElement('span', { class: trim.leftThumb }); - this.elements.bar.rightThumb = createElement('span', { class: trim.rightThumb }); + this.elements.bar.leftThumb = createElement( + 'span', + extend({ + class: trim.leftThumb, + role: 'slider', + 'aria-valuemin': 0, + 'aria-valuemax': this.player.duration, + 'aria-valuenow': this.startTime, + 'aria-valuetext': formatTime(this.startTime), + 'aria-label': i18n.get('trimStart', this.player.config), + }), + ); + + // Create the trim bar thumb elements + this.elements.bar.rightThumb = createElement( + 'span', + extend({ + class: trim.rightThumb, + role: 'slider', + 'aria-valuemin': 0, + 'aria-valuemax': this.player.duration, + 'aria-valuenow': this.endTime, + 'aria-valuetext': formatTime(this.endTime), + 'aria-label': i18n.get('trimEnd', this.player.config), + }), + ); // Add the thumbs to the bar this.elements.bar.appendChild(this.elements.bar.leftThumb); @@ -200,6 +226,9 @@ class Trim { this.setStartTime(percentage); // Set the timestamp of the current trim handle position bar.leftThumb.timeContainer.time.innerText = formatTime(this.startTime); + // Update the aria-value + bar.leftThumb.setAttribute('aria-valuenow', this.startTime); + bar.leftThumb.setAttribute('aria-valuetext', formatTime(this.startTime)); // Update seek position to match the left thumbs position if less than the current left thumb position } else if (this.editing === rightThumb) { const end = percentage - parseFloat(bar.style.left); @@ -207,6 +236,9 @@ class Trim { this.setEndTime(end); // Set the timestamp of the current trim handle position bar.rightThumb.timeContainer.time.innerText = formatTime(this.endTime); + // Update the aria-value + bar.rightThumb.setAttribute('aria-valuenow', this.endTime); + bar.rightThumb.setAttribute('aria-valuetext', formatTime(this.endTime)); } } From c9f832927398835ee95f6dc3429bb4ee8b6e866f Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Mon, 6 Apr 2020 11:47:52 +0100 Subject: [PATCH 08/20] Nit pick fixes for trimming tool PR --- readme.md | 6 +++--- src/js/config/defaults.js | 2 +- src/js/controls.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index ee4a55666..56647e050 100644 --- a/readme.md +++ b/readme.md @@ -448,7 +448,7 @@ player.source = { ], poster: '/path/to/poster.jpg', previewThumbnails: { - src: '/path/to/thumbnails.vtt', + src: '/path/to/thumbnails.vtt' }, tracks: [ { @@ -579,7 +579,7 @@ player.on('ready', event => { | `waiting` | Sent when the requested operation (such as playback) is delayed pending the completion of another operation (such as a seek). | | `emptied` | he media has become empty; for example, this event is sent if the media has already been loaded (or partially loaded), and the `load()` method is called to reload it. | | `cuechange` | Sent when a `TextTrack` has changed the currently displaying cues. | -| `entertrim` | Sent when the player enters the trimming tool | +| `entertrim` | Sent when the player enters the trimming tool. | | `exittrim` | Sent when the player exits the trimming tool mode. | | `trimchange` | Sent when the trimming region has changed. | | `error` | Sent when an error occurs. The element's `error` attribute contains more information. | @@ -637,7 +637,7 @@ Fullscreen in Plyr is supported by all browsers that [currently support it](http # Trimming -It's possible to create a trim region for your video using the trim control. You will need to generate the clip yourself through listening to the 'trimchange' event and will return the start and end of the selected region. +It's possible to create a trim region from your video using the trim mode. This is useful when creating clips or repeating loops. The 'trimchange' event will give you the start and end time of the selected region. # Browser support diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js index a601109e3..db908e310 100644 --- a/src/js/config/defaults.js +++ b/src/js/config/defaults.js @@ -145,7 +145,7 @@ const defaults = { 'pip', 'airplay', // 'download', - 'trim', + // 'trim', 'fullscreen', ], settings: ['captions', 'quality', 'speed'], diff --git a/src/js/controls.js b/src/js/controls.js index a97b315f6..ca769aa75 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -53,7 +53,7 @@ const controls = { // Buttons this.elements.buttons = { - play: getElements.call(this, this.config.selectors.buttons), + play: getElements.call(this, this.config.selectors.buttons.play), pause: getElement.call(this, this.config.selectors.buttons.pause), restart: getElement.call(this, this.config.selectors.buttons.restart), rewind: getElement.call(this, this.config.selectors.buttons.rewind), From 5780e0d233a58ceee404c2eab701c6858ed91321 Mon Sep 17 00:00:00 2001 From: DanielHuntleySBG <51910014+DanielHuntleySBG@users.noreply.github.com> Date: Mon, 6 Apr 2020 11:49:19 +0100 Subject: [PATCH 09/20] Remove plyr.svg from pull request --- dist/plyr.svg | 1 - 1 file changed, 1 deletion(-) delete mode 100644 dist/plyr.svg diff --git a/dist/plyr.svg b/dist/plyr.svg deleted file mode 100644 index d16084d7a..000000000 --- a/dist/plyr.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From e13be14ea74e717f5df4ac27b70d6560f5ada4dd Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Mon, 6 Apr 2020 17:45:35 +0100 Subject: [PATCH 10/20] Trimming: On source change update whether or not to show the trimming tool --- src/js/source.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/js/source.js b/src/js/source.js index cb8067468..2c299ed45 100644 --- a/src/js/source.js +++ b/src/js/source.js @@ -147,6 +147,9 @@ const source = { } } + // Update trimming tool support + this.trim.update(); + // Update the fullscreen support this.fullscreen.update(); }, From 2ab8a88b7e96f331ea394837eeae3c74e4a35e09 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Tue, 7 Apr 2020 11:10:30 +0100 Subject: [PATCH 11/20] Video trimming bug fix: When switching sources a new trimming object was not created | We should only display the trim thumb time tooltip when displaying preview thumbnails as a seek tooltip is displayed --- src/js/plugins/trim.js | 28 +++++++++++++++++++++++++--- src/js/source.js | 12 ++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index b64e8cab6..fbd3ec8ec 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -89,7 +89,11 @@ class Trim { if (is.element(seekElement) && this.loaded) { this.createTrimBar(seekElement); this.createTrimBarThumbs(); - this.createThumbTime(); + /* Only display the thumb time when displaying preview thumbnails as we don't want to display the whole thumbnail but + want to keep the same styling */ + if (this.player.config.previewThumbnails.enabled) { + this.createThumbTime(); + } } } @@ -225,7 +229,9 @@ class Trim { bar.style.left = `${percentage}%`; this.setStartTime(percentage); // Set the timestamp of the current trim handle position - bar.leftThumb.timeContainer.time.innerText = formatTime(this.startTime); + if (bar.leftThumb.timeContainer) { + bar.leftThumb.timeContainer.time.innerText = formatTime(this.startTime); + } // Update the aria-value bar.leftThumb.setAttribute('aria-valuenow', this.startTime); bar.leftThumb.setAttribute('aria-valuetext', formatTime(this.startTime)); @@ -235,7 +241,9 @@ class Trim { bar.style.width = `${end}%`; this.setEndTime(end); // Set the timestamp of the current trim handle position - bar.rightThumb.timeContainer.time.innerText = formatTime(this.endTime); + if (bar.rightThumb.timeContainer) { + bar.rightThumb.timeContainer.time.innerText = formatTime(this.endTime); + } // Update the aria-value bar.rightThumb.setAttribute('aria-valuenow', this.endTime); bar.rightThumb.setAttribute('aria-valuetext', formatTime(this.endTime)); @@ -243,6 +251,10 @@ class Trim { } toggleTimeContainer(element, toggle = false) { + if (!element.timeContainer) { + return; + } + const className = this.player.config.classNames.trim.timeContainerShown; element.timeContainer.classList.toggle(className, toggle); } @@ -302,6 +314,16 @@ class Trim { toggleClass(this.player.elements.container, this.player.config.classNames.trim.enabled, this.enabled); } + destroy() { + // Remove the elements with listeners on + if (this.elements.bar && this.elements.bar.leftThumb) { + this.elements.bar.leftThumb.remove(); + } + if (this.elements.bar && this.elements.bar.rightThumb) { + this.elements.bar.rightThumb.remove(); + } + } + // Enter trim tool enter() { if (!this.enabled) { diff --git a/src/js/source.js b/src/js/source.js index 2c299ed45..8edbc2338 100644 --- a/src/js/source.js +++ b/src/js/source.js @@ -6,6 +6,7 @@ import { providers } from './config/types'; import html5 from './html5'; import media from './media'; import PreviewThumbnails from './plugins/preview-thumbnails'; +import Trim from './plugins/trim'; import support from './support'; import ui from './ui'; import { createElement, insertElement, removeElement } from './utils/elements'; @@ -147,6 +148,17 @@ const source = { } } + // Create new instance of trim plugin + if (this.trim && this.trim.loaded) { + this.trim.destroy(); + this.trim = null; + } + + // Create new instance if it is still enabled + if (this.config.trim.enabled) { + this.trim = new Trim(this); + } + // Update trimming tool support this.trim.update(); From c0fa6d58d11f5d5c02b6ffb719baa897c8b84110 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Wed, 8 Apr 2020 09:45:30 +0100 Subject: [PATCH 12/20] Trimming: Change structure of triggerevent messages so that they are in the correct format and pass a valid element --- src/js/plugins/trim.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index fbd3ec8ec..5be0fae21 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -115,7 +115,7 @@ class Trim { this.elements.bar.style.width = `${end.toString()}%`; seekElement.appendChild(this.elements.bar); - triggerEvent.call(this.player, this.trimTime, 'trimchange'); + triggerEvent.call(this.player, this.player.media, 'trimchange', false, this.trimTime); } // Add trim length thumbs to the timeline @@ -197,11 +197,11 @@ class Trim { if ((type === 'mouseup' || type === 'touchend') && this.editing === leftThumb) { this.editing = null; this.toggleTimeContainer(this.elements.bar.leftThumb, false); - triggerEvent.call(this.player, this.trimTime, 'trimchange'); + triggerEvent.call(this.player, this.player.media, 'trimchange', false, this.trimTime); } else if ((type === 'mouseup' || type === 'touchend') && this.editing === rightThumb) { this.editing = null; this.toggleTimeContainer(this.elements.bar.rightThumb, false); - triggerEvent.call(this.player, this.trimTime, 'trimchange'); + triggerEvent.call(this.player, this.player.media, 'trimchange', false, this.trimTime); } else if ((type === 'mousedown' || type === 'touchstart') && target.classList.contains(leftThumb)) { this.editing = leftThumb; this.toggleTimeContainer(this.elements.bar.leftThumb, true); @@ -299,7 +299,7 @@ class Trim { } // Trigger an event - triggerEvent.call(this.player, this.media, this.active ? 'entertrim' : 'exittrim', true); + triggerEvent.call(this.player, this.player.media, this.active ? 'entertrim' : 'exittrim', true, this.trimTime); } // Update UI @@ -316,11 +316,8 @@ class Trim { destroy() { // Remove the elements with listeners on - if (this.elements.bar && this.elements.bar.leftThumb) { - this.elements.bar.leftThumb.remove(); - } - if (this.elements.bar && this.elements.bar.rightThumb) { - this.elements.bar.rightThumb.remove(); + if (this.elements.bar && !is.empty(this.elements.bar)) { + this.elements.bar.remove(); } } From 81d77ebceb312c374465ce35ed7e4383f786e516 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Thu, 9 Apr 2020 12:24:43 +0100 Subject: [PATCH 13/20] Remove Timeupdate event listener when changing source as two instances existed causing issues --- src/js/plugins/trim.js | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index 5be0fae21..0906174a3 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -20,6 +20,7 @@ class Trim { this.defaultTrimLength = 30; this.startTime = 0; this.endTime = 0; + this.timeUpdateFunction = this.timeUpdate.bind(this); this.elements = { bar: {}, }; @@ -259,9 +260,26 @@ class Trim { element.timeContainer.classList.toggle(className, toggle); } + // Set the seektime to the start of the trim timeline, if the seektime is outside of the region. + timeUpdate() { + if (!this.active || !this.trimming || !this.player.playing || this.editing) { + return; + } + + const { currentTime } = this.player; + if (currentTime < this.startTime || currentTime >= this.endTime) { + this.player.currentTime = this.startTime; + + if (currentTime >= this.endTime) { + this.player.pause(); + } + } + } + listeners() { /* Prevent the trim tool from being added until the player is in a playable state - If the user has pressed the trim tool before this event has fired, show the tool */ + If the user has pressed the trim tool before this event has fired, show the tool + */ this.player.once('canplay', () => { this.loaded = true; if (this.trimming) { @@ -269,21 +287,11 @@ class Trim { } }); - // Set the seektime to the start of the trim timeline, if the seektime is outside of the region. - this.player.on('timeupdate', () => { - if (!this.active || !this.trimming || !this.player.playing || this.editing) { - return; - } - - const { currentTime } = this.player; - if (currentTime < this.startTime || currentTime >= this.endTime) { - this.player.currentTime = this.startTime; - - if (currentTime >= this.endTime) { - this.player.pause(); - } - } - }); + /* Listen for time changes so we can reset the seek point to within the clip. + Additionally, use the reference to the binding so we can remove and create a new instance of this listener + when we change source + */ + this.player.on('timeupdate', this.timeUpdateFunction); } // On toggle of trim control, trigger event @@ -319,6 +327,8 @@ class Trim { if (this.elements.bar && !is.empty(this.elements.bar)) { this.elements.bar.remove(); } + + this.player.off('timeupdate', this.timeUpdateFunction); } // Enter trim tool From ee73dc4cd7d487d8d5513348b0c153fcc2ac0a29 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Thu, 9 Apr 2020 14:43:32 +0100 Subject: [PATCH 14/20] Convert the default trim length to percent as for longer videos it was showing only a very small region --- src/js/plugins/trim.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index 0906174a3..6365fbf86 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -17,7 +17,7 @@ class Trim { this.loaded = false; this.trimming = false; this.editing = false; - this.defaultTrimLength = 30; + this.defaultTrimLength = 20; // Trim length in percent this.startTime = 0; this.endTime = 0; this.timeUpdateFunction = this.timeUpdate.bind(this); @@ -68,7 +68,7 @@ class Trim { // Store the trim end time in seconds setEndTime(percentage) { - this.endTime = this.startTime + this.player.media.duration * (parseFloat(percentage) / 100); + this.endTime = this.player.media.duration * (parseFloat(percentage) / 100); } // Show the trim toolbar on the timeline @@ -100,9 +100,9 @@ class Trim { // Add trim bar to the timeline createTrimBar(seekElement) { - // Set the trim bar from the current seek time percentage to x number of seconds after and limit the end percentage to 100% + // Set the trim bar from the current seek time percentage to x percent after and limit the end percentage to 100% const start = this.player.elements.inputs.seek.value; - const end = Math.min(parseFloat(start) + (100 / this.player.duration) * this.defaultTrimLength, 100 - start); + const end = Math.min(parseFloat(start) + this.defaultTrimLength, 100); // Store the start and end video percentages in seconds this.setStartTime(start); @@ -113,7 +113,7 @@ class Trim { }); this.elements.bar.style.left = `${start.toString()}%`; - this.elements.bar.style.width = `${end.toString()}%`; + this.elements.bar.style.width = `${end - start.toString()}%`; seekElement.appendChild(this.elements.bar); triggerEvent.call(this.player, this.player.media, 'trimchange', false, this.trimTime); @@ -238,9 +238,8 @@ class Trim { bar.leftThumb.setAttribute('aria-valuetext', formatTime(this.startTime)); // Update seek position to match the left thumbs position if less than the current left thumb position } else if (this.editing === rightThumb) { - const end = percentage - parseFloat(bar.style.left); - bar.style.width = `${end}%`; - this.setEndTime(end); + bar.style.width = `${percentage - parseFloat(bar.style.left)}%`; + this.setEndTime(percentage); // Set the timestamp of the current trim handle position if (bar.rightThumb.timeContainer) { bar.rightThumb.timeContainer.time.innerText = formatTime(this.endTime); From 5e5313784dee9702cf80cbd7c3b19b9b0e7f9a1c Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Thu, 9 Apr 2020 17:19:00 +0100 Subject: [PATCH 15/20] Addition of a couple additional api endpoints to get and set the start and end points of the trimming range --- readme.md | 2 ++ src/js/plyr.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/readme.md b/readme.md index 56647e050..2b985e32a 100644 --- a/readme.md +++ b/readme.md @@ -421,6 +421,8 @@ player.fullscreen.active; // false; | `pip`¹ | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ (on MacOS Sierra+ and iOS 10+) and Chrome 70+. | | `ratio` | ✓ | ✓ | Gets or sets the video aspect ratio. The setter accepts a string in the same format as the `ratio` option. | | `download` | ✓ | ✓ | Gets or sets the URL for the download button. The setter accepts a string containing a valid absolute URL. | +| `trimStartTime` | ✓ | ✓ | Gets or sets the trimming range start time. The setter accepts a float in seconds. | +| `trimEndTime` | ✓ | ✓ | Gets or sets the trimming range end time. The setter accepts a float in seconds. | 1. HTML5 only diff --git a/src/js/plyr.js b/src/js/plyr.js index 8391412a0..20626bf80 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1042,6 +1042,60 @@ class Plyr { return this.trim.trimming; } + /** + * Set trim range start time + * @param {Number} input - set the trim start point in seconds. Defaults to 0 (the start) + */ + set trimStartTime(input) { + // Bail if media duration isn't available yet + if (!this.duration) { + return; + } + + // Validate input + const inputIsValid = is.number(input) && input > 0; + + // Set + this.trim.startTime = inputIsValid ? Math.min(input, this.duration) : 0; + + // Logging + this.debug.log(`Seeking to ${this.trim.startTime} seconds`); + } + + /** + * Get trim range start time + */ + get trimStartTime() { + return this.trim.startTime; + } + + /** + * Set trim range end time + * @param {Number} input - set the trim start point in seconds. Defaults to 0 (the start) + */ + set trimEndTime(input) { + // Bail if media duration isn't available yet + if (!this.duration) { + return; + } + + // Validate input + const inputIsValid = is.number(input) && input > 0; + + // Set + this.trim.endTime = inputIsValid ? Math.min(input, this.duration) : 0; + + // Logging + this.debug.log(`Seeking to ${this.trim.endTime} seconds`); + } + + /** + * Get trim range End time + */ + get trimEndTime() { + return this.trim.startTime; + } + /** * Trigger the airplay dialog * TODO: update player with state, support, enabled From 83ab236db89a0b66adc15f0e5da3508e1af8bf29 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Thu, 9 Apr 2020 18:21:05 +0100 Subject: [PATCH 16/20] Update to use the existing clamp function | Improve commenting for setting the trim length | Prevent the end value of the trimming region from being before the start --- src/js/plugins/trim.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index 6365fbf86..621ce849e 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -6,6 +6,7 @@ import { createElement, toggleClass, toggleHidden } from '../utils/elements'; import { on, triggerEvent } from '../utils/events'; import i18n from '../utils/i18n'; import is from '../utils/is'; +import { clamp } from '../utils/numbers.js'; import { extend } from '../utils/objects'; import { formatTime } from '../utils/time'; @@ -215,36 +216,43 @@ class Trim { setTrimLength(event) { if (!this.editing) return; + // Calculate hover position const clientRect = this.player.elements.progress.getBoundingClientRect(); - // Mouse Position const xPos = event.type === 'touchmove' ? event.touches[0].pageX : event.pageX; - // Percentage must be between 0 and 100 - const percentage = Math.max(Math.min((100 / clientRect.width) * (xPos - clientRect.left), 100), 0); - /* Alter width of the trim region - - If left thumb selected increase width and keep right hand side in same position - - If right thumb selected just decrease the width */ + const percentage = clamp((100 / clientRect.width) * (xPos - clientRect.left), 0, 100); + // Get the current position of the trim tool bar const { leftThumb, rightThumb } = this.player.config.classNames.trim; const { bar } = this.elements; + + // Update the position of the trim range tool if (this.editing === leftThumb) { + // Set the width to be in the position previously bar.style.width = `${parseFloat(bar.style.width) - (percentage - parseFloat(bar.style.left))}%`; + // Increase the left thumb bar.style.left = `${percentage}%`; + // Store and convert the start percentage to time this.setStartTime(percentage); // Set the timestamp of the current trim handle position if (bar.leftThumb.timeContainer) { bar.leftThumb.timeContainer.time.innerText = formatTime(this.startTime); } - // Update the aria-value + // Update the aria-value and text bar.leftThumb.setAttribute('aria-valuenow', this.startTime); bar.leftThumb.setAttribute('aria-valuetext', formatTime(this.startTime)); - // Update seek position to match the left thumbs position if less than the current left thumb position } else if (this.editing === rightThumb) { + // Prevent the end time to be before the start time + if (percentage <= parseFloat(bar.style.left)) { + return; + } + // Update the width of trim bar (right thumb) bar.style.width = `${percentage - parseFloat(bar.style.left)}%`; + // Store and convert the start percentage to time this.setEndTime(percentage); // Set the timestamp of the current trim handle position if (bar.rightThumb.timeContainer) { bar.rightThumb.timeContainer.time.innerText = formatTime(this.endTime); } - // Update the aria-value + // Update the aria-value and text bar.rightThumb.setAttribute('aria-valuenow', this.endTime); bar.rightThumb.setAttribute('aria-valuetext', formatTime(this.endTime)); } From bc2fbc4bec2d61c3dca201896270da87761338d4 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Sat, 11 Apr 2020 17:17:38 +0100 Subject: [PATCH 17/20] Remove getter and setter for trimming from plyr.js as it can be accessed via the trimming object and update documentation --- readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 2b985e32a..684d6f8e2 100644 --- a/readme.md +++ b/readme.md @@ -366,6 +366,9 @@ player.fullscreen.enter(); // Enter fullscreen | `fullscreen.enter()` | - | Enter fullscreen. If fullscreen is not supported, a fallback "full window/viewport" is used instead. | | `fullscreen.exit()` | - | Exit fullscreen. | | `fullscreen.toggle()` | - | Toggle fullscreen. | +| `trim.enter()` | - | Enter Trimming tool. | +| `trim.exit()` | - | Exit Trimming tool. | +| `trim.toggle()` | - | Toggle Trimming tool. | | `airplay()` | - | Trigger the airplay dialog on supported devices. | | `toggleControls(toggle)` | Boolean | Toggle the controls (video only). Takes optional truthy value to force it on/off. | | `on(event, function)` | String, Function | Add an event listener for the specified event. | @@ -450,7 +453,7 @@ player.source = { ], poster: '/path/to/poster.jpg', previewThumbnails: { - src: '/path/to/thumbnails.vtt' + src: '/path/to/thumbnails.vtt', }, tracks: [ { From bdf4718efb05b4365af143d4ae4a199c436c471e Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Sat, 11 Apr 2020 17:44:42 +0100 Subject: [PATCH 18/20] Remove settrimstart and settrimend from plyr.js as they can be set from the trim class: unsure whether a setter should exist for both of these and what its naming convention would be, so have kept it set via the variables directly --- readme.md | 4 ++-- src/js/plugins/trim.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 684d6f8e2..435ddadf4 100644 --- a/readme.md +++ b/readme.md @@ -424,8 +424,8 @@ player.fullscreen.active; // false; | `pip`¹ | ✓ | ✓ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ (on MacOS Sierra+ and iOS 10+) and Chrome 70+. | | `ratio` | ✓ | ✓ | Gets or sets the video aspect ratio. The setter accepts a string in the same format as the `ratio` option. | | `download` | ✓ | ✓ | Gets or sets the URL for the download button. The setter accepts a string containing a valid absolute URL. | -| `trimStartTime` | ✓ | ✓ | Gets or sets the trimming range start time. The setter accepts a float in seconds. | -| `trimEndTime` | ✓ | ✓ | Gets or sets the trimming range end time. The setter accepts a float in seconds. | +| `trim.startTime` | ✓ | ✓ | Gets or sets the trimming range start time. The setter accepts a float in seconds. | +| `trim.endTime` | ✓ | ✓ | Gets or sets the trimming range end time. The setter accepts a float in seconds. | 1. HTML5 only diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index 621ce849e..c18702286 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -6,7 +6,7 @@ import { createElement, toggleClass, toggleHidden } from '../utils/elements'; import { on, triggerEvent } from '../utils/events'; import i18n from '../utils/i18n'; import is from '../utils/is'; -import { clamp } from '../utils/numbers.js'; +import { clamp } from '../utils/numbers'; import { extend } from '../utils/objects'; import { formatTime } from '../utils/time'; From 04941a0a1b540bab08619e31394776138845cdaa Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Tue, 14 Apr 2020 09:26:04 +0100 Subject: [PATCH 19/20] Calling trim.enter or trim.exit did not update the video trim control so have moved the logic to inside the enter and exit function | Additionally removed toggling trimming from plyr.js as it can all be set via trim.xxx --- src/js/plugins/trim.js | 5 ++- src/js/plyr.js | 69 ------------------------------------------ 2 files changed, 4 insertions(+), 70 deletions(-) diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index c18702286..c1942c409 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -345,6 +345,8 @@ class Trim { } this.trimming = true; this.showTrimTool(); + + this.onChange(); } // Exit trim tool @@ -354,6 +356,8 @@ class Trim { } this.trimming = false; this.hideTrimTool(); + + this.onChange(); } // Toggle state @@ -363,7 +367,6 @@ class Trim { } else { this.exit(); } - this.onChange(); } } diff --git a/src/js/plyr.js b/src/js/plyr.js index 20626bf80..c71a30674 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1027,75 +1027,6 @@ class Plyr { return this.media === document.pictureInPictureElement; } - /** - * Toggle Trim - * @param {Boolean} input - Whether to show trimming tool - */ - set trimming(input) { - this.trim.toggle.call(this, input, false); - } - - /** - * Get the current trim state - */ - get trimming() { - return this.trim.trimming; - } - - /** - * Set trim range start time - * @param {Number} input - set the trim start point in seconds. Defaults to 0 (the start) - */ - set trimStartTime(input) { - // Bail if media duration isn't available yet - if (!this.duration) { - return; - } - - // Validate input - const inputIsValid = is.number(input) && input > 0; - - // Set - this.trim.startTime = inputIsValid ? Math.min(input, this.duration) : 0; - - // Logging - this.debug.log(`Seeking to ${this.trim.startTime} seconds`); - } - - /** - * Get trim range start time - */ - get trimStartTime() { - return this.trim.startTime; - } - - /** - * Set trim range end time - * @param {Number} input - set the trim start point in seconds. Defaults to 0 (the start) - */ - set trimEndTime(input) { - // Bail if media duration isn't available yet - if (!this.duration) { - return; - } - - // Validate input - const inputIsValid = is.number(input) && input > 0; - - // Set - this.trim.endTime = inputIsValid ? Math.min(input, this.duration) : 0; - - // Logging - this.debug.log(`Seeking to ${this.trim.endTime} seconds`); - } - - /** - * Get trim range End time - */ - get trimEndTime() { - return this.trim.startTime; - } - /** * Trigger the airplay dialog * TODO: update player with state, support, enabled From 64dc1fd29112db8e5d437d0d9d6da04b6c06d764 Mon Sep 17 00:00:00 2001 From: Danielh112 Date: Mon, 20 Apr 2020 12:36:30 +0100 Subject: [PATCH 20/20] Fix to Trimming tool, to prevent the start time being before the end time --- src/js/plugins/trim.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/js/plugins/trim.js b/src/js/plugins/trim.js index c1942c409..08b03880f 100644 --- a/src/js/plugins/trim.js +++ b/src/js/plugins/trim.js @@ -232,6 +232,10 @@ class Trim { bar.style.left = `${percentage}%`; // Store and convert the start percentage to time this.setStartTime(percentage); + // Prevent the end time being before the start time + if (this.startTime > this.endTime) { + this.setEndTime(percentage); + } // Set the timestamp of the current trim handle position if (bar.leftThumb.timeContainer) { bar.leftThumb.timeContainer.time.innerText = formatTime(this.startTime);