Open AnasProgrammer2 opened 3 years ago
well coincidently i was going to write a issue like this but found this one. so i am in favor for this feature
well coincidently i was going to write a issue like this but found this one. so i am in favor for this feature
thanks ,
well coincidently i was going to write a issue like this but found this one. so i am in favor for this feature
waiting your update
Can add just buttons to forward 10sec?
Can add just buttons to forward 10sec?
that might also be great, let's wait for a dev/maintainer/owner of this project to view it. All the best to awesome devs who built this player 😄
I personally like this feature as well. Attached are how Facebook and Twitter handle this on mobile devices.
Interesting to point out, and to keep in mind, this behavior is only implemented for touch-screen/mobile devices. It does not exist in the desktop experience on either platform.
Here are 3 screenshots of how Twitter and Facebook handle it:
i suggest if not double click, then the facebook type buttons could be added after checking if the device is a mobile, tablet device or a laptop, PC
Any news about this feature?
No news as of now, although I have managed to inject my custom buttons using some code after the player loads. I use brython in my projects so you can translate this into javascript if needed. Icons are svg images from google's icon set. Here is the code used:
<script type="text/javascript">
function insertAfter(newNode, existingNode) {
existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);
}
</script>
<script type="text/python">
from browser import document, window, html, bind, timer
def wait_and_init():
elm = document.select(".plyr__controls__item")[0]
forward_elm = html.IMG("", **{"src": "{{ url_for('static', filename='static/images/forward_10_white.svg') }}",
"class": "qcontrols",
"id": "forward-10"})
replay_elm = html.IMG("", **{"src": "{{ url_for('static', filename='static/images/replay_10_white.svg') }}",
"class": "qcontrols",
"id": "replay-10"})
window.insertAfter(forward_elm, elm)
window.insertAfter(replay_elm, elm)
@bind(replay_elm, "click")
def replay_10_seconds(evt):
window.player.rewind(10)
@bind(forward_elm, "click")
def forward_10_seconds(evt):
window.player.forward(10)
timer.set_timeout(wait_and_init, 1000)
</script>
Interestingly, I have managed to build the double click feature from this page but with some modifications to make it look just a little better. Here is the scripting part and html part (all in javascript):
First add the following css to your website:
<style type="text/css">
.player {
width:100%;
border: 5px solid rgba(0,0,0,0.2);
box-shadow: 0 0 20px rgba(0,0,0,0.2);
position: relative;
font-size: 0;
overflow: hidden;
}
video{
width:100%;
display:block;
}
.video-container{
position: relative;
overflow: hidden;
}
.video-forward-notify{
text-align: center;
width:30%;
height:200%;
border-radius:100% 0 0 100%;
position: absolute;
display:flex;
flex-direction: row;
right: 0%;
top:-50%;
}
.video-forward-notify .icon{
justify-content:flex-start;
align-items:center;
margin: auto 0 auto 40%;
color: white;
}
.video-rewind-notify{
text-align: center;
width:30%;
height:200%;
border-radius:0 100% 100% 0;
position: absolute;
display:flex;
flex-direction: row;
left: 0;
top:-50%;
}
.video-rewind-notify .icon{
justify-content:flex-start;
align-items:center;
margin: auto 0 auto 40%;
color: white;
}
.icon i{
display:block;
}
.notification{
transition: background 0.8s;
background: rgba(200,200,200,.4) radial-gradient(circle, transparent 1%, rgba(200,200,200,.4) 1%) center/15000%;
pointer-events:none;
display: none;
}
i{
font-style:normal;
}
.animate-in{
display:flex;
animation: ripple 1s forwards;
}
.animate-in i{
display:block;
}
.animate-in.forward i{
padding-bottom:2px;
}
.animate-in.forward i{
animation: fadeInLeft .7s;
}
.animate-in.rewind i{
animation: fadeInRight .7s;
}
@keyframes ripple{
0% {
background-color: rgba(200,200,200,.4);
background-size: 100%;
transition: background 0s;
opacity:1;
}
100% {
transition: background 0.8s;
background: rgba(200,200,200,.4) radial-gradient(circle, transparent 1%, rgba(200,200,200,.4) 1%) center/15000%;
display: flex;
opacity:0;
}
}
@keyframes fadeInLeft {
0% {
opacity: 0;
transform: translateX(-20px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeInRight {
0% {
opacity: 0;
transform: translateX(0px);
}
100% {
opacity: 1;
transform: translateX(-20px);
}
}
font12{
font-size:12px;
}
</style>
Now, do the following with the video tag that is going to be initialised with plyr:
<div class="video-container">
<video controls playsinline autoplay id="video_player">
</video>
<div class="video-rewind-notify rewind notification">
<div class="rewind-icon icon">
<i class="left-triangle triangle">◀◀◀</i>
<span class="rewind font12">10 seconds</span>
</div>
</div>
<div class="video-forward-notify forward notification">
<div class="forward-icon icon">
<i class="right-triangle triangle">▶▶▶</i>
<span class="forward font12">10 seconds</span>
</div>
</div>
</div>
With the above done for video tag replacement add the following scripts to the end of body tag:
<script type="text/javascript">
//grab the video dom element
const video = document.querySelector('video');
const notifications = document.querySelectorAll('.notification');
const forwardNotificationValue = document.querySelector('.video-forward-notify span');
const rewindNotificationValue = document.querySelector('.video-rewind-notify span');
let timer;
let rewindSpeed = 0;
let forwardSpeed = 0;
//function for double click event listener on the video
//todo change those variable to html5 data attributes
function updateCurrentTime(delta){
let isRewinding = delta < 0;
if(isRewinding){
rewindSpeed = rewindSpeed + delta;
forwardSpeed = 0;
}else{
forwardSpeed = forwardSpeed + delta;
rewindSpeed = 0;
}
//clear the timeout
clearTimeout(timer);
let speed = (isRewinding ? rewindSpeed : forwardSpeed);
video.currentTime = video.currentTime + speed;
let NotificationValue = isRewinding ? rewindNotificationValue : forwardNotificationValue ;
NotificationValue.innerHTML = `${Math.abs(speed)} seconds`;
//reset accumulator within 2 seconds of a double click
timer = setTimeout(function(){
rewindSpeed = 0;
forwardSpeed = 0;
}, 2000); // you can edit this delay value for the timeout, i have it set for 2 seconds
console.log(`updated time: ${video.currentTime}`);
}
function animateNotificationIn(isRewinding){
isRewinding ? notifications[0].classList.add('animate-in') : notifications[1].classList.add('animate-in');
}
function animateNotificationOut(){
this.classList.remove('animate-in');
}
function forwardVideo(){
updateCurrentTime(10);
animateNotificationIn(false);
}
function rewindVideo(){
updateCurrentTime(-10);
animateNotificationIn(true);
}
//Event Handlers
function doubleClickHandler(e){
console.log(`current time: ${video.currentTime}`);
const videoWidth = video.offsetWidth;
(e.offsetX < videoWidth/2) ? rewindVideo() : forwardVideo();
}
function togglePlay(){
video.paused ? video.play() : video.pause();
}
// If you want it to work on desktop browsers, just replace the condition with true
if (window.is_tablet_browser() || window.is_mobile_browser()) {
//Event Listeners
video.addEventListener('click', togglePlay);
video.addEventListener('dblclick', doubleClickHandler);
notifications.forEach(function(notification){
notification.addEventListener('animationend', animateNotificationOut);
});
}
</script>
Make sure to add this utility script tag somewhere in the head tag:
<script type="text/javascript">
function is_mobile_browser () {
let check = false;
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
return check;
}
function is_tablet_browser () {
let check = false;
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
return check;
}
</script>
Now remove the doubleclick event for plyr to fullscreen so it does not interfere with our double click event to forward or rewind. Add the following just after initializing plyr:
player.eventListeners.forEach(function(eventListener) {
if(eventListener.type === 'dblclick') {
eventListener.element.removeEventListener(eventListener.type, eventListener.callback, eventListener.options);
};
});
With the above modifications made. When your website is opened in a tablet or a mobile browser, the double tap to forward or rewind will be available. Note: This can work on desktop and laptops but it's disabled by default in the script. You can modify the provided script to make it work on desktops also
Okk I've done it with some css and js Limitations, one video per page (you can twick the code and by pass that
const byId = document.getElementById.bind(document),
byClass = document.getElementsByClassName.bind(document),
byTag = document.getElementsByTagName.bind(document),
byName = document.getElementsByName.bind(document),
createElement = document.createElement.bind(document);
var player = new Plyr('#player');
// Remove all dblclick stuffs
player.eventListeners.forEach(function (eventListener) {
if (eventListener.type === 'dblclick') {
eventListener.element.removeEventListener(eventListener.type, eventListener.callback, eventListener.options);
}
});
// Create overlay that will show the skipped time
const skip_ol = createElement("div");
skip_ol.id = "plyr__time_skip"
byClass("plyr")[0].appendChild(skip_ol)
// A class to manage multi click count and remember last clicked side (may cause issue otherwise)
class multiclick_counter {
constructor() {
this.timers = []; // collection of timers. Important
this.count = 0; // click count
this.reseted = 0; // before resetting what was the count
this.last_side = null; // L C R 3sides
}
clicked() {
this.count += 1
var xcount = this.count; // will be checked if click count increased in the time
this.timers.push(setTimeout(this.reset.bind(this, xcount), 500)); // wait till 500ms for next click
return this.count
}
reset_count(n) {
// Reset count if clicked on the different side
this.reseted = this.count
this.count = n
for (var i = 0; i < this.timers.length; i++) {
clearTimeout(this.timers[i]);
}
this.timer = []
}
reset(xcount) {
if (this.count > xcount) { return } // return if clicked after timer started
// Reset otherwise
this.count = 0;
this.last_side = null;
this.reseted = 0;
skip_ol.style.opacity = "0";
this.timer = []
}
}
var counter = new multiclick_counter();
const poster = byClass("plyr__poster")[0]
// We will target the poster since this is the only thing sits between video and controls
poster.onclick = function (e) {
const count = counter.clicked()
if (count < 2) { return } // if not double click
const rect = e.target.getBoundingClientRect();
const x = e.clientX - rect.left; //x position within the element.
const y = e.clientY - rect.top; //y position within the element.
console.log("Left? : " + x + " ; Top? : " + y + ".");
// The relative position of click on video
const width = e.target.offsetWidth;
const perc = x * 100 / width;
var panic = true; // panic if the side needs to be checked
var last_click = counter.last_side
if (last_click == null) {
panic = false
}
if (perc < 40) {
if(player.currentTime==0){
return // won't seek beyond 0
}
counter.last_side = "L"
if (panic && last_click != "L") {
counter.reset_count(1)
return
}
skip_ol.style.opacity = "0.9";
player.rewind()
skip_ol.innerText = "⫷⪡" + "\\n" + ((count - 1) * 10) + "s";
}
else if (perc > 60) {
if(player.currentTime==player.duration){
return // won't seek beyond duration
}
counter.last_side = "R"
if (panic && last_click != "R") {
counter.reset_count(1)
return
}
skip_ol.style.opacity = "0.9";
last_click = "R"
player.forward()
skip_ol.innerText = "⪢⫸ " + "\n" + ((count - 1) * 10) + "s";
}
else {
player.togglePlay()
counter.last_click = "C"
}
}
The CSS part:
#plyr__time_skip {
background: #111111cc;
border: 0;
border-radius: 50%;
color: #fff;
left: 50%;
min-width: 80px;
width: min-content;
max-width: 100px;
max-height: 90px;
opacity: 0;
display: table-cell;
text-align: center;
vertical-align: middle;
transform: translate(-50%, -50%);
padding-top: 20px;
position: absolute;
top: 50%;
transition: 1s;
z-index: 3;
pointer-events: none;
box-shadow: 0px 0px 45px #000000;
}
I used @RaSan147 code for my needs with own implementation and multiple videos.
for (let video of document.querySelectorAll('video')) {
const player = new Plyr(video);
player.on('ready', () => {
const root = video.closest('.plyr-video');
// remove double click handlers
player.eventListeners.ForEach (function (EventListener) {
if (eventListener.type === 'dblclick') {
eventListener.element.removeEventListener(eventListener.type, eventListener.callback, eventListener.options);
}
});
const poster = root.querySelector('.plyr__poster');
const timeSkip = document.createElement('div');
const resetState = () => {
poster.clickedTimes = 0;
poster.lastSideClicked = undefined;
};
timeSkip.className = 'plyr__time-skip';
poster.parentNode.insertBefore(timeSkip, poster);
poster.clickedTimes = 0;
// handle clicks
poster.addEventListener('click', function (event) {
poster.clickedTimes++;
if (poster.resetTimeout) {
clearTimeout(poster.resetTimeout);
}
poster.resetTimeout = setTimeout(resetState, 1000);
// handle only double click
if (poster.clickedTimes < 2) {
return;
}
// find click position
const percentage = (event.clientX - event.target.getBoundingClientRect().left) * 100 / event.target.offsetWidth;
if (percentage < 40) {
if (player.currentTime === 0
|| (typeof poster.lastSideClicked !== 'undefined' && poster.lastSideClicked !== 'L')
) {
clearTimeout(poster.resetTimeout);
resetState();
return;
}
timeSkip.innerText = '<<\n' + ((poster.clickedTimes - 1) * 10) + 's';
timeSkip.classList.add('is-left');
timeSkip.classList.remove('is-right');
timeSkip.classList.remove('is-animated');
setTimeout(() => timeSkip.classList.add('is-animated'), 1);
poster.lastSideClicked = 'L';
player.rewind();
} else if (percentage > 60) {
if (player.currentTime === player.duration
|| (typeof poster.lastSideClicked !== 'undefined' && poster.lastSideClicked !== 'R')
) {
clearTimeout(poster.resetTimeout);
resetState();
return;
}
timeSkip.innerText = '>>\n' + ((poster.clickedTimes - 1) * 10) + 's';
timeSkip.classList.add('is-right');
timeSkip.classList.remove('is-left');
timeSkip.classList.remove('is-animated');
setTimeout(() => timeSkip.classList.add('is-animated'), 1);
poster.lastSideClicked = 'R';
player.forward();
} else {
poster.lastSideClicked = 'C';
}
});
});
}
@keyframes plyr__time-skip {
40% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.plyr {
&__time-skip {
position: absolute;
top: 0;
bottom: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
width: 40%;
opacity: 0;
pointer-events: none;
&.is-left {
left: 0;
background: linear-gradient(90deg, rgba(0, 0, 0, 0.5) 0%, transparent 100%);
}
&.is-right {
right: 0;
background: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, 0.5) 100%);
}
&.is-animated {
animation: plyr__time-skip ease 1s forwards;
}
}
}
hello , please update player with feature ( double tap to fast forward +5 Sec or more )