NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Youtube Player Always On Top
// @namespace almaceleste
// @version 0.5.0
// @description this code makes the youtube player visible while scrolling
// @description:ru этот код делает плеер youtube видимым при прокрутке
// @author (ɔ) Paola Captanovska
// @license AGPL-3.0-or-later; http://www.gnu.org/licenses/agpl.txt
// @icon https://s.ytimg.com/yts/img/favicon-vfl8qSV2F.ico
// @icon64 https://s.ytimg.com/yts/img/favicon_96-vflW9Ec0w.png
// @homepageURL https://greasyfork.org/en/users/174037-almaceleste
// @homepageURL https://openuserjs.org/users/almaceleste
// @homepageURL https://github.com/almaceleste/userscripts
// @supportURL https://github.com/almaceleste/userscripts/issues
// @updateURL https://github.com/almaceleste/userscripts/raw/master/src/Youtube_Player_Always_On_Top.user.js
// @downloadURL https://github.com/almaceleste/userscripts/raw/master/src/Youtube_Player_Always_On_Top.user.js
// @downloadURL https://openuserjs.org/install/almaceleste/Youtube_Player_Always_On_Top.user.js
// @require https://code.jquery.com/jquery-3.3.1.js
// @require https://raw.githubusercontent.com/uzairfarooq/arrive/master/minified/arrive.min.js
// @require https://openuserjs.org/src/libs/sizzle/GM_config.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_openInTab
// @match https://www.youtube.com/*
// @match https://www.youtube.com/watch*
// ==/UserScript==
// ==OpenUserJS==
// @author almaceleste
// ==/OpenUserJS==
let app = {};
var player = {};
var video = {};
var info = {};
var header = {};
var progressbar = {};
var options = {
existing: true
};
const pattern = /^\/watch/;
const chat = '#chat';
const leftside = '#content #columns > #primary > #primary-inner';
const rightside = '#secondary-inner';
header.id = '#masthead-container';
player.id = `${leftside} > #player`;
player.buttons = `${player.id} .ytp-chrome-top-buttons`;
player.cardsbutton = `${player.buttons} > .ytp-cards-button`;
player.outer = `${player.id} > #player-container-outer`;
player.inner = `${player.outer} > #player-container-inner`;
player.container = `${player.inner} > #player-container`;
player.controls = `${player.container} .ytp-chrome-bottom`;
player.minimizebutton = `${player.container} #ytp-minimize-button`;
progressbar.id = `${player.controls} > .ytp-progress-bar-container`;
progressbar.progressbar = `${progressbar.id} > .ytp-progress-bar`;
progressbar.hovercontainer = `${progressbar.progressbar} .ytp-chapter-hover-container`;
progressbar.padding = `${progressbar.hovercontainer} .ytp-progress-bar-padding`;
progressbar.progresslist = `${progressbar.hovercontainer} .ytp-progress-list`;
progressbar.progressplay = `${progressbar.progresslist} .ytp-play-progress`;
progressbar.progresshover = `${progressbar.progresslist} .ytp-hover-progress`;
progressbar.scrubber = `${progressbar.id} .ytp-scrubber-container`;
progressbar.scrubberbutton = `${progressbar.scrubber} > .ytp-scrubber-button`;
player.tooltip = `${player.container} .ytp-tooltip.ytp-bottom.ytp-preview`;
player.tooltippreview = `${player.tooltip} > .ytp-tooltip-bg`;
player.tooltiptext = `${player.tooltip} > .ytp-tooltip-text-wrapper`;
player.ctxmenu = '.ytp-popup.ytp-contextmenu';
player.optmenu = 'iron-dropdown.style-scope.ytd-popup-container';
video.id = `${player.id} #ytd-player > #container video`;
video.content = `${player.container} .ytp-iv-video-content`;
app.processing = false;
video.minimized = false;
info.id = `${leftside} > #info`;
info.container = `${info.id} > #info-contents #container`;
const windowcss = `
#ytpaotCfg {
background-color: lightblue;
}
#ytpaotCfg .reset_holder {
float: left;
position: relative;
bottom: -1em;
}
#ytpaotCfg .saveclose_buttons {
margin: .7em;
}
#ytpaotCfg_field_url {
background: none !important;
border: none;
cursor: pointer;
padding: 0 !important;
text-decoration: underline;
}
#ytpaotCfg_field_url:hover,
#ytpaotCfg_resetLink:hover {
filter: drop-shadow(0 0 1px dodgerblue);
}
`;
const iframecss = `
height: 26.5em;
width: 43em;
border: 1px solid;
border-radius: 3px;
position: fixed;
z-index: 9999;
`;
GM_registerMenuCommand('Youtube Player Always On Top Settings', opencfg);
function opencfg(){
GM_config.open();
ytpaotCfg.style = iframecss;
}
GM_config.init({
id: 'ytpaotCfg',
title: 'Youtube Player Always On Top',
fields:
{
infoOnTop:
{
section: ['', 'Settings'],
label: 'info panel always visible (require reload)',
labelPos: 'right',
type: 'checkbox',
default: true,
},
padding:
{
label: 'box border (padding) size in px (require reload)',
labelPos: 'left',
type: 'select',
options: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],
default: '5',
},
minimum:
{
label: 'minimized size (in % of maximized size)',
labelPos: 'left',
type: 'select',
options: ['35', '40', '45', '50', '55', '60', '65', '70', '75', '80', '85', '90'],
default: '55',
},
background:
{
label: 'box background (apply on size change)',
labelPos: 'left',
type: 'select',
options: [
'body',
'white',
'whitesmoke',
'lightgray',
'gray',
'dimgray',
'lightblue',
'deepskyblue',
'magenta',
'lime',
'green',
'cyan'],
default: 'dimgray',
},
url:
{
section: ['', 'Support'],
label: 'almaceleste.github.io',
type: 'button',
click: () => {
GM_openInTab('https://almaceleste.github.io', {
active: true,
insert: true,
setParent: true
});
}
},
},
css: windowcss,
events:
{
save: function() {
GM_config.close();
}
},
});
function getStyle(id, prop){
let style = $(id).attr('style');
if (style) {
let start = style.lastIndexOf(`${prop}:`);
prop = style.substring(start, style.indexOf(';', start));
prop = prop.substring(prop.lastIndexOf(':') + 1, prop.lastIndexOf('px')).trim();
}
return +prop;
}
function setProgress(){
let bar = $(progressbar.progressbar);
let progress = bar.attr('aria-valuenow')/bar.attr('aria-valuemax');
$(progressbar.progresshover).css({
'left': `${progressbar.width*progress}px`,
});
let left = $(progressbar.scrubberbutton).width()/2;
let scrubber = $(progressbar.scrubber).css('transform');
if (scrubber) {
scrubber = scrubber.substring(scrubber.lastIndexOf('(') + 1, scrubber.lastIndexOf(')'))
scrubber = scrubber.split(',')[4];
left = player.width*progress - left - scrubber;
$(progressbar.scrubber).css({
'left': `${left}px`
});
}
let top = getStyle(player.tooltip, 'top');
// let height = getStyle(player.tooltippreview, 'height');
top = player.height - $(player.controls).height() - $(progressbar.padding).height() - $(player.tooltip).height() - $(player.tooltiptext).height() - top;
$(player.tooltip).css({
transform: `translateY(${top}px)`,
willChange: 'transform'
});
}
function prepare(){
header.height = $(header.id).height();
player.top = header.height;
player.background = GM_config.get('background');
if (player.background == 'body') {
player.background = $('body').css('background-color');
}
player.padding = +GM_config.get('padding');
console.log('doThings:', player.width, player.height);
$(player.id).css({
backgroundColor: player.background,
padding: `${player.padding}px`,
position: 'fixed',
top: `${player.top}px`,
willChange: 'transform',
zIndex: '2000' // '2200'
});
$(video.id).css({
willChange: 'transform'
});
$(player.outer).css({
// willChange: 'transform'
});
$(player.inner).css({
// willChange: 'transform'
});
$(player.container).css({
// willChange: 'transform'
});
$(player.controls).css({
transform: 'scaleX(.982)',
// willChange: 'transform'
});
$(progressbar.progresshover).css({
willChange: 'transform'
});
$(progressbar.scrubber).css({
willChange: 'transform'
});
$(video.content).css({
willChange: 'transform'
});
if (GM_config.get('infoOnTop')){
info.background = player.background;
info.padding = player.padding;
info.height = $(info.id).height();
$(info.id).css({
backgroundColor: info.background,
padding: `0 ${info.padding}px`,
position: 'fixed',
willChange: 'transform',
zIndex: '1001'
})
}
$(leftside).css({
});
}
function minimize(){
app.processing = true;
let percent = GM_config.get('minimum');
header.height = $(header.id).height();
player.height = video.height*percent/100;
player.width = video.width*percent/100;
player.top = header.height;
console.log('minimize:', player.width, player.height);
let margin = player.height;
$(player.id).css({
height: `${player.height}px`,
width: `${player.width}px`,
});
$(video.id).css({
height: `${player.height}px`,
width: `${player.width}px`,
});
$(player.outer).css({
height: `${player.height}px`,
width: `${player.width}px`,
});
$(player.inner).css({
paddingTop: `${player.height}px`,
width: `${player.width}px`,
});
$(player.container).css({
height: `${player.height}px`,
width: `${player.width}px`,
});
$(player.controls).css({
left: '0',
width: `${player.width}`,
});
$(player.minimizebutton).attr('title', 'Maximize');
$(player.minimizebutton).css({
transform: 'scale(0.7)'
});
progressbar.width = player.width;
$(progressbar.id).css({
width: `${progressbar.width}px`,
});
$(progressbar.hovercontainer).css({
width: `${progressbar.width}px`,
});
$(progressbar.padding).css({
width: `${progressbar.width}px`,
});
$(progressbar.progresslist).css({
width: `${progressbar.width}px`,
});
$(video.content).css({
height: `${player.height}px`,
width: `${player.width}px`,
});
if (GM_config.get('infoOnTop')){
info.width = player.width;
info.height = $(info.id).height();
info.top = player.height + player.top;
$(info.id).css({
top: `${info.top}px`,
width: `${info.width}px`,
})
margin += info.height;
}
$(leftside).css({
marginTop: `${margin}px`
});
app.processing = false;
player.minimized = true;
}
function maximize(){
app.processing = true;
header.height = $(header.id).height();
player.height = video.height;
player.width = video.width;
player.top = header.height;
console.log('maximize:', player.width, player.height);
let margin = player.height;
$(player.id).css({
height: `${player.height}px`,
width: `${player.width}px`,
});
$(video.id).css({
height: `${player.height}px`,
width: `${player.width}px`,
});
$(player.outer).css({
height: `${player.height}px`,
width: `${player.width}px`,
});
$(player.inner).css({
paddingTop: `${player.height}px`,
width: `${player.width}px`,
});
$(player.container).css({
height: `${player.height}px`,
width: `${player.width}px`,
});
$(player.controls).css({
left: '0',
width: `${player.width}`,
});
$(player.minimizebutton).attr('title', 'Minimize');
$(player.minimizebutton).css({
transform: 'scale(1)'
});
progressbar.width = player.width;
$(progressbar.id).css({
width: `${progressbar.width}px`,
});
$(progressbar.hovercontainer).css({
width: `${progressbar.width}px`,
});
$(progressbar.padding).css({
width: `${progressbar.width}px`,
});
$(progressbar.progresslist).css({
width: `${progressbar.width}px`,
});
$(video.content).css({
height: `${player.height}px`,
width: `${player.width}px`,
});
if (GM_config.get('infoOnTop')){
info.width = player.width;
info.height = $(info.id).height();
info.top = player.height + player.top;
$(info.id).css({
top: `${info.top}px`,
width: `${info.width}px`,
})
margin += info.height;
}
$(leftside).css({
marginTop: `${margin}px`
});
app.processing = false;
player.minimized = false;
}
function animate(id, start, end){
$({x: start}).animate({x: end}, {
duration: 250,
step: (x) => {
$(id).css({
transform: `scale(${x})`
});
}
});
}
function waitForVideo(){
$(document).arrive(video.id, options, () => {
video.exists = true;
video.height = $(video.id).height();
video.width = $(video.id).width();
if (player.minimized){
minimize();
}
else {
maximize();
}
});
}
function matchPath(pattern){
const pathname = window.location.pathname;
if (pathname.match(pattern)) {
return true;
}
else {
return false;
}
}
function createButton(){
let padding = $(player.cardsbutton).css('padding-top');
let size = $(player.cardsbutton).width();
if (!size) {
size = 36;
}
size = size;
let svg = `<svg height='100%' version='1.1' viewBox='0 0 ${size} ${size}' width='100%'>
<rect fill='none' stroke='white' stroke-width='3' stroke-linejoin='round'
x='${size/8}' y='${size/4}' height='${size/2}' width='${size*6/8}'
/>
</svg>`;
// add minimize/maximize button
$('<div id="ytp-minimize-button" title="Minimize"></div>').insertBefore(player.buttons).css({
cursor: 'pointer',
display: 'none',
height: `${size}px`,
paddingTop: padding,
width: `${size}px`,
}).append(svg).on({
mouseenter: () => {
if (player.minimized){
animate(player.minimizebutton, 0.7, 1);
}
else {
animate(player.minimizebutton, 1, 0.7);
}
},
mouseleave: () => {
if (player.minimized){
animate(player.minimizebutton, 1, 0.7);
}
else {
animate(player.minimizebutton, 0.7, 1);
}
},
click: () => {
if (player.minimized) {
maximize();
}
else {
minimize();
}
}});
}
(function() {
'use strict';
// onload
$(document).ready(() => {
if (matchPath(pattern)){
app.watch = true;
doThings();
}
else {
app.watch = false;
$(window).on({
transitionend: (e) => {
const c = 'yt-page-navigation-progress';
if (e.target.id = 'progress' && e.target.classList.contains(c)) {
if (matchPath(pattern)) {
app.watch = true;
doThings();
}
else {
app.watch = false;
}
}
}
});
}
});
function doThings(){
$(document).arrive(player.id, options, () => {
prepare();
waitForVideo();
createButton();
});
// $(document).arrive('#scriptTag', options, () => {
// let json = JSON.parse(document.getElementById('scriptTag').innerText);
// console.log('duration:', json.duration);
// });
$(document).arrive(player.controls, options, () => {
var scrubberInterval;
$(player.id).on({
mouseenter: () => {
scrubberInterval = setInterval(() => {
setProgress();
}, 250);
$(player.minimizebutton).css({
'display': 'block'
});
},
mouseleave: () => {
clearInterval(scrubberInterval);
$(player.minimizebutton).css({
'display': 'none'
});
}
});
});
$(window).on({
fullscreenchange: () => {
// console.log('fullscreenchange:', window.fullscreenElement, document.fullscreen);
if (!document.fullscreenElement){
waitForVideo();
}
},
resize: () => {
if (video.exists) {
let options = {
attributes: true,
attributeFilter: ['style'],
attributeOldValue: true
}
let element = document.querySelector(video.id);
let observer = new MutationObserver((mutations) => {
mutations.forEach((m) => {
if (!app.processing){
video.height = $(video.id).height();
video.width = $(video.id).width();
if (!app.processing){
prepare();
if (video.minimized){
minimize();
}
else {
maximize();
}
}
}
observer.disconnect();
})
});
observer.observe(element, options);
}
},
scroll: () =>{
let scrollTop = $(window).scrollTop();
if (scrollTop > 50) {
if (!player.minimized){
minimize();
}
}
else {
if (player.minimized) {
maximize();
}
}
}
});
}
})();