Major updates
2
.gitignore
vendored
@@ -1 +1 @@
|
||||
wip/
|
||||
old/
|
||||
|
||||
346
README.md
@@ -1,346 +0,0 @@
|
||||
|
||||
# GoodTube
|
||||
|
||||
Hello and welcome! I'm glad you're here.
|
||||
|
||||
What is GoodTube you ask? It's a little plugin for Youtube (that just works) and;
|
||||
|
||||
- REMOVES 100% OF PAGE AND VIDEO ADS.
|
||||
- Allows background play (so you can turn your phone screen off and keep listening).
|
||||
- Allows you to download the audio / video / entire playlist in up to 8K quality with the click of a button (at the bottom of the player).
|
||||
- Works on both desktop and mobile (iOS and Android).
|
||||
- Works in all major browsers (Chrome, Firefox, Opera, etc).
|
||||
- Proxies in Youtube videos from different servers (in up to 8k quality).
|
||||
- Removes shorts.
|
||||
- Removes unwanted search results ("You might also like this", "Other people also watched", etc).
|
||||
- Removes thumbnails for other recommended videos that pop up when a video finishes (I really hate these, try without it's nice).
|
||||
- Keeps you up to date with the latest version automatically.
|
||||
|
||||
And it keeps the good stuff like;
|
||||
|
||||
- The beloved algorithm / watch history.
|
||||
- Keyboard shortcuts.
|
||||
- Subtitles.
|
||||
- Chapters.
|
||||
- Autoplay.
|
||||
- Playlists.
|
||||
- Picture in picture / the miniplayer.
|
||||
- Theater mode.
|
||||
- Live streams.
|
||||
|
||||
It's easy to install too;
|
||||
|
||||
- [How to install on desktop](#how-to-install-on-desktop)
|
||||
- [How to install on Android (mobile)](#how-to-install-on-android-mobile)
|
||||
- [How to install on iOS / iPhone (mobile)](#how-to-install-on-ios--iphone-mobile)
|
||||
|
||||
Here's some screenshots;
|
||||
| | |
|
||||
|--|--|
|
||||
|
||||
## How to install on Desktop
|
||||
|
||||
1. Disable your other adblockers! You can do this for Youtube only.
|
||||
|
||||
|
||||
2. Install this browser extension "Tampermonkey":
|
||||
|
||||
https://www.tampermonkey.net/
|
||||
|
||||
|
||||
3. Once that's done, simply click on this link and press "Install":
|
||||
|
||||
https://github.com/goodtube4u/goodtube/raw/main/goodtube.user.js
|
||||
|
||||
4. If you're using CHROME
|
||||
|
||||
You need to turn on developer mode for this extension. Check out the screenshots below for instructions.
|
||||
|
||||

|
||||
|
||||
|
||||
5. If you're using FIREFOX
|
||||
|
||||
You need to enable autoplay. Check out the screenshot below for instructions.
|
||||
|
||||

|
||||
|
||||
|
||||
That's it. You're good to go. Open up Youtube and happy days.
|
||||
|
||||
|
||||
## How to install on Android (mobile)
|
||||
|
||||
This will take you a few minutes, because mobiles are generally sort of annoying...
|
||||
|
||||
Just follow the steps below and I promise it'll work for you! :)
|
||||
|
||||
1. First off you'll need to install this app called "Firefox Nightly". You can find it on Google Play.
|
||||
(Basically it's an official release of Firefox, but it also allows you to install browser extensions.)
|
||||
|
||||
**Pro tip - I've recently discovered that you can also install extensions on normal Firefox, so you may not need to download Firefox Nightly! Other than that, the steps are the same.**
|
||||
|
||||
|
||||
2. Once it's installed, open Firefox Nightly.
|
||||
Now click the 3 dots down the bottom right, and go to "Settings":
|
||||
|
||||

|
||||
|
||||
|
||||
3. Scroll down and go to "Extensions":
|
||||
|
||||

|
||||
|
||||
|
||||
4. Add the extension "Tampermonkey":
|
||||
|
||||

|
||||
|
||||
|
||||
5. Now go back to the main screen of Firefox Nightly (just hit back back back until you're there).
|
||||
|
||||
6. Then go to the following webpage:
|
||||
https://github.com/goodtube4u/goodtube/raw/main/goodtube.user.js
|
||||
|
||||
|
||||
7. Click "Install":
|
||||
|
||||

|
||||
|
||||
|
||||
**That's it. You're good to go. Open up Youtube in Firefox Nightly and happy days!!**
|
||||
|
||||
I recommend using Firefox Nightly just like you would the Youtube app. Put it on your home screen somewhere / make Youtube the homepage for an even smoother experience :)
|
||||
|
||||
*Please note: If you see a mostly blank screen on the Youtube homepage, don't worry! This is normal. Just search for something.*
|
||||
|
||||
*Simply sign into Youtube and the homepage will be full of your favorite videos once again.*
|
||||
|
||||
|
||||
## How to install on iOS / iPhone (mobile)
|
||||
|
||||
This will take you a few minutes, because mobiles are generally sort of annoying...
|
||||
|
||||
Just follow the steps below and I promise it'll work for you! :)
|
||||
|
||||
1. First off you'll need to install this app called "Orion Browser". You can find it on the App Store. (Basically it's an alternative browser that allows you to install browser extensions.)
|
||||
|
||||
2. Once it's installed, open Orion Browser.
|
||||
|
||||
3. Go to Extensions.
|
||||
|
||||
4. Install an extension called "Violentmonkey" (do NOT try and use Tampermonkey instead, it doesn't work for iOS).
|
||||
|
||||
5. Now go to the following webpage: https://github.com/goodtube4u/goodtube/raw/main/goodtube.user.js
|
||||
|
||||
6. Click "Install" on the page the pops up.
|
||||
|
||||
|
||||
**That's it. You're good to go. Open up Youtube in Orion Browser and happy days!!**
|
||||
|
||||
I recommend using Orion Browser just like you would the Youtube app. Put it on your home screen somewhere / make Youtube the homepage for an even smoother experience :)
|
||||
|
||||
*Please note: If you see a mostly blank screen on the Youtube homepage, don't worry! This is normal. Just search for something.*
|
||||
|
||||
*Simply sign into Youtube and the homepage will be full of your favorite videos once again.*
|
||||
|
||||
|
||||
## Why?
|
||||
Well lately I've been raging as Youtube have successfully stopped most adblockers from working (or they only work sometimes). You've probably been experiencing this same nightmare on and off.
|
||||
|
||||
I'm a programmer by trade so decided to try and fix this for everyone.
|
||||
|
||||
I'll never pay a company that is slapping ads on one of the largest archives of music / film / art in the world and blackmailing me to remove them. They really have a social responsibility that isn't being taken seriously...
|
||||
|
||||
So anyway, screw em. Install this little plugin and enjoy no ads 🎉
|
||||
|
||||
This took around 5 weeks to create and a thousand black coffees. I do hope you enjoy it.
|
||||
|
||||
Any questions, you can contact me at: goodtube4u@hotmail.com
|
||||
|
||||
I'm dedicated to helping every single user get this working, so really - if you have any problems at all hit me up!
|
||||
|
||||
|
||||
## Attributions
|
||||
|
||||
Many thanks to https://cobalt.tools for providing the amazing API we're using to download stuff!
|
||||
|
||||
You can also use this website to download from other platforms like SoundCloud, give it a try :)
|
||||
|
||||
|
||||
## (Optional) Host your own local video server - to make this load videos FAST!
|
||||
|
||||
- This is for advanced users only.
|
||||
- This is not for phones. You can only do this on a desktop computer (Windows, Mac and Linux are all supported).
|
||||
- Doing this will significantly speed up GoodTube! You should get normal Youtube speed or very close to.
|
||||
|
||||
|
||||
**Here's how you do it:**
|
||||
|
||||
1. Install Docker Desktop (https://www.docker.com/products/docker-desktop/)
|
||||
|
||||
2. Install Git (https://git-scm.com/downloads)
|
||||
|
||||
3. Open Terminal / Command Prompt and enter the following commands
|
||||
```
|
||||
cd c:/
|
||||
git clone https://github.com/iv-org/invidious.git
|
||||
```
|
||||
|
||||
4. Edit the following file in a text editor like Notepad:
|
||||
`c:/invidious/docker-compose.yml`
|
||||
|
||||
5. Delete all the code in there and replace it with:
|
||||
```
|
||||
version: "3"
|
||||
services:
|
||||
|
||||
invidious:
|
||||
image: quay.io/invidious/invidious:latest
|
||||
# image: quay.io/invidious/invidious:latest-arm64 # ARM64/AArch64 devices
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000"
|
||||
environment:
|
||||
# Please read the following file for a comprehensive list of all available
|
||||
# configuration options and their associated syntax:
|
||||
# https://github.com/iv-org/invidious/blob/master/config/config.example.yml
|
||||
INVIDIOUS_CONFIG: |
|
||||
db:
|
||||
dbname: invidious
|
||||
user: kemal
|
||||
password: kemal
|
||||
host: invidious-db
|
||||
port: 5432
|
||||
check_tables: true
|
||||
# external_port:
|
||||
# domain:
|
||||
# https_only: false
|
||||
# statistics_enabled: false
|
||||
hmac_key: "goodtube4u"
|
||||
healthcheck:
|
||||
test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/trending || exit 1
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 2
|
||||
logging:
|
||||
options:
|
||||
max-size: "1G"
|
||||
max-file: "4"
|
||||
depends_on:
|
||||
- invidious-db
|
||||
|
||||
invidious-db:
|
||||
image: docker.io/library/postgres:14
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgresdata:/var/lib/postgresql/data
|
||||
- ./config/sql:/config/sql
|
||||
- ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
|
||||
environment:
|
||||
POSTGRES_DB: invidious
|
||||
POSTGRES_USER: kemal
|
||||
POSTGRES_PASSWORD: kemal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
|
||||
volumes:
|
||||
postgresdata:
|
||||
|
||||
```
|
||||
|
||||
6. Open Terminal / Command Prompt and enter the following commands
|
||||
```
|
||||
cd c:/invidious
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
7. Let it run for a few minutes, you'll see a bunch of text appearing while it installs everything. Eventually it will stop outputting text and you can close Terminal / Command Prompt.
|
||||
|
||||
8. Open Docker Desktop.
|
||||
|
||||
9. On the "Containers" tab you will see a thing called "Invidious". Press the play arrow to start it.
|
||||
|
||||
10. Check it's working by going to http://127.0.0.1:3000 in your browser. If you see an instance of Invidious you're good to go!
|
||||
|
||||
11. Now that's done, go to https://www.youtube.com/?goodtube_local=true in your browser to enable the local server. You only need to do this once, the setting will be remembered.
|
||||
|
||||
12. Now you can visit Youtube like normal, but at the top of the "Video Source" list you will see a server called "LOCAL". This is your local video server, recommended you select the "Automatic" mode from the video servers list. This will try your local server first, and if that fails, fallback to using the normal servers.
|
||||
|
||||
13. As long as you've got your Invidious instance running in Docker Desktop, then this local video server will work and should be SUPER FAST. Enjoy :)
|
||||
|
||||
Note: If you want to turn this off, go to https://www.youtube.com/?goodtube_local=false in your browser. You only need to do this once, the setting will be remembered.
|
||||
|
||||
|
||||
## (Optional) Add custom video servers
|
||||
|
||||
- This is for advanced users only.
|
||||
- It allows you to add your own video servers to the list.
|
||||
- If you're hosting your own video server as per the above instructions, but need to change the web address or settings, this provides an easy way to do that.
|
||||
|
||||
**To add a custom video server**
|
||||
|
||||
Visit `https://www.youtube.com` with the following GET params:
|
||||
|
||||
```
|
||||
goodtube_customserver_0_name=My custom server
|
||||
goodtube_customserver_0_type=2
|
||||
goodtube_customserver_0_proxy=true
|
||||
goodtube_customserver_0_url=https://mycustomserver.com
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
`https://www.youtube.com?goodtube_customserver_0_name=My custom server&goodtube_customserver_0_type=2&goodtube_customserver_0_proxy=true&goodtube_customserver_0_url=https://myawesomeserver.com`
|
||||
|
||||
You can do this for up to 10 servers. Just change the `0` to `1`, `2`, `3` and so on. Example:
|
||||
|
||||
`goodtube_customserver_0_name` is the first server
|
||||
|
||||
`goodtube_customserver_1_name` is the second server
|
||||
|
||||
|
||||
You only need to do this once. It will remember the setting until you disable it.
|
||||
|
||||
|
||||
**To remove a custom video server**
|
||||
|
||||
Visit `https://www.youtube.com` with the following GET param:
|
||||
|
||||
`https://www.youtube.com?goodtube_customserver_0=false`
|
||||
|
||||
|
||||
**Server options**
|
||||
|
||||
Name (goodtube_customserver_XXX_name):
|
||||
|
||||
```
|
||||
The name of the server
|
||||
```
|
||||
|
||||
Type (goodtube_customserver_XXX_type):
|
||||
|
||||
```
|
||||
1 = Invidious, 360p only
|
||||
2 = Invidious, DASH stream - all qualities
|
||||
3 = Piped - all qualities
|
||||
```
|
||||
|
||||
Proxy (goodtube_customserver_XXX_proxy):
|
||||
|
||||
```
|
||||
true = Proxy all video traffic through the server (recommended)
|
||||
false = Do NOT proxy traffic through the server (not recommended)
|
||||
```
|
||||
|
||||
URL (goodtube_customserver_XXX_url):
|
||||
|
||||
```
|
||||
The web address of the server. Make sure this does not have a trailing slash. For example:
|
||||
https://myawesomeserver.com
|
||||
```
|
||||
|
||||
**PLEASE NOTE**:
|
||||
|
||||
- Custom video servers MUST be served over `https`. They will not work if they are served over `http`.
|
||||
- You can use `http` if the custom server web address is local, for example: `http://127.0.0.1:3000`
|
||||
2113
beta/test.user.js
1
css/assets.min.css
vendored
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* pthumbnails
|
||||
* @version 1.2.0
|
||||
* @copyright 2020 Rigel Kent <sendmemail@rigelk.eu>
|
||||
* @license MIT
|
||||
*/
|
||||
.video-js.vjs-vtt-thumbnails{display:block}.video-js .vjs-vtt-thumbnail-display{opacity:0;position:absolute;transition:opacity .5s;bottom:85%;pointer-events:none;box-shadow:0 0 7px rgba(0,0,0,0.6)}.video-js .vjs-vtt-thumbnail-time{position:absolute;bottom:0;transform:translateX(-50%);background:rgba(0,0,0,0.6);padding:0.5em;border-top-left-radius:10%;border-top-right-radius:10%}
|
||||
8191
goodtube.js
1
goodtube.min.js
vendored
@@ -1,21 +1,23 @@
|
||||
// ==UserScript==
|
||||
// @name GoodTube
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 5.010
|
||||
// @description Loads Youtube videos from different sources. Also removes ads, shorts, etc.
|
||||
// @version 2.000
|
||||
// @description Removes 100% of Youtube ads.
|
||||
// @author GoodTube
|
||||
// @match https://*.youtube.com/*
|
||||
// @updateURL https://github.com/goodtube4u/goodtube/raw/refs/heads/main/beta/goodtube-embed.user.js
|
||||
// @downloadURL https://github.com/goodtube4u/goodtube/raw/refs/heads/main/beta/goodtube-embed.user.js
|
||||
// @match *://m.youtube.com/*
|
||||
// @match *://www.youtube.com/*
|
||||
// @match *://youtube.com/*
|
||||
// @match *://*.wikipedia.org/*
|
||||
// @icon https://cdn-icons-png.flaticon.com/256/1384/1384060.png
|
||||
// @run-at document-start
|
||||
// @updateURL https://github.com/goodtube4u/goodtube/raw/main/goodtube.user.js
|
||||
// @downloadURL https://github.com/goodtube4u/goodtube/raw/main/goodtube.user.js
|
||||
// @noframes
|
||||
// ==/UserScript==
|
||||
|
||||
// This now automatically loads the latest version. This means that you will never need to update manually again :)
|
||||
// To view the full source code go here: https://github.com/goodtube4u/goodtube/blob/main/goodtube.js
|
||||
|
||||
(function() {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Bypass CSP restrictions, introduced by the latest Chrome updates
|
||||
@@ -51,22 +53,22 @@
|
||||
loadAttempts++;
|
||||
|
||||
// Load GoodTube
|
||||
fetch('https://raw.githubusercontent.com/goodtube4u/GoodTube/main/goodtube.min.js?i='+Date.now())
|
||||
// Success
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
// Put GoodTube code into a <script> tag
|
||||
let element = document.createElement('script');
|
||||
element.innerHTML = data;
|
||||
document.head.appendChild(element);
|
||||
})
|
||||
// Error
|
||||
.catch((error) => {
|
||||
// Try again after 500ms
|
||||
setTimeout(function() {
|
||||
goodTube_load(loadAttempts);
|
||||
}, 500);
|
||||
});
|
||||
fetch('https://raw.githubusercontent.com/goodtube4u/GoodTube/main/goodtube.js?i=' + Date.now())
|
||||
// Success
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
// Put GoodTube code into a <script> tag
|
||||
let element = document.createElement('script');
|
||||
element.innerHTML = data;
|
||||
document.head.appendChild(element);
|
||||
})
|
||||
// Error
|
||||
.catch((error) => {
|
||||
// Try again after 500ms
|
||||
setTimeout(function () {
|
||||
goodTube_load(loadAttempts);
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
// Load GoodTube
|
||||
|
||||
74
goodtube.user.min.js
vendored
@@ -1,74 +0,0 @@
|
||||
// ==UserScript==
|
||||
// @name GoodTube
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 5.010
|
||||
// @description Loads Youtube videos from different sources. Also removes ads, shorts, etc.
|
||||
// @author GoodTube
|
||||
// @match https://*.youtube.com/*
|
||||
// @icon https://cdn-icons-png.flaticon.com/256/1384/1384060.png
|
||||
// @run-at document-start
|
||||
// @updateURL https://github.com/goodtube4u/goodtube/raw/main/goodtube.user.js
|
||||
// @downloadURL https://github.com/goodtube4u/goodtube/raw/main/goodtube.user.js
|
||||
// @noframes
|
||||
// ==/UserScript==
|
||||
|
||||
// This now automatically loads the latest version. This means that you will never need to update manually again :)
|
||||
// To view the full source code go here: https://github.com/goodtube4u/goodtube/blob/main/goodtube.js
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Bypass CSP restrictions, introduced by the latest Chrome updates
|
||||
if (window.trustedTypes && window.trustedTypes.createPolicy && !window.trustedTypes.defaultPolicy) {
|
||||
window.trustedTypes.createPolicy('default', {
|
||||
createHTML: string => string,
|
||||
createScriptURL: string => string,
|
||||
createScript: string => string
|
||||
});
|
||||
}
|
||||
|
||||
// Define load function
|
||||
function goodTube_load(loadAttempts) {
|
||||
// If it's the first load attempt
|
||||
if (loadAttempts === 0) {
|
||||
// Debug message
|
||||
console.log('\n==================================================\n ______ ________ __\n / ____/___ ____ ____/ /_ __/_ __/ /_ ___\n / / __/ __ \\/ __ \\/ __ / / / / / / / __ \\/ _ \\\n / /_/ / /_/ / /_/ / /_/ / / / / /_/ / /_/ / __/\n \\____/\\____/\\____/\\____/ /_/ \\____/_____/\\___/\n\n==================================================');
|
||||
console.log('[GoodTube] Initiating...');
|
||||
}
|
||||
|
||||
// Only re-attempt to load the GoodTube 10 times
|
||||
if (loadAttempts >= 9) {
|
||||
// Debug message
|
||||
console.log('[GoodTube] GoodTube could not be loaded');
|
||||
|
||||
// Show prompt
|
||||
alert('GoodTube could not be loaded! Please refresh the page to try again.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Increment the load attempts
|
||||
loadAttempts++;
|
||||
|
||||
// Load GoodTube
|
||||
fetch('https://raw.githubusercontent.com/goodtube4u/GoodTube/main/goodtube.min.js?i='+Date.now())
|
||||
// Success
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
// Put GoodTube code into a <script> tag
|
||||
let element = document.createElement('script');
|
||||
element.innerHTML = data;
|
||||
document.head.appendChild(element);
|
||||
})
|
||||
// Error
|
||||
.catch((error) => {
|
||||
// Try again after 500ms
|
||||
setTimeout(function() {
|
||||
goodTube_load(loadAttempts);
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
// Load GoodTube
|
||||
goodTube_load(0);
|
||||
})();
|
||||
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 580 KiB |
|
Before Width: | Height: | Size: 894 KiB |
|
Before Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 82 KiB |
8
js/assets.min.js
vendored
40
js/video.min.js
vendored
@@ -1,312 +0,0 @@
|
||||
/*! @name videojs-hls-quality-selector @version 2.0.0 @license MIT */
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['video.js'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsHlsQualitySelector = factory(global.videojs));
|
||||
}(this, (function (videojs) { 'use strict';
|
||||
|
||||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
||||
|
||||
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
|
||||
|
||||
var version = "2.0.0";
|
||||
|
||||
const MenuButton = videojs__default['default'].getComponent('MenuButton');
|
||||
const Menu = videojs__default['default'].getComponent('Menu');
|
||||
const Component = videojs__default['default'].getComponent('Component');
|
||||
const Dom = videojs__default['default'].dom;
|
||||
|
||||
/**
|
||||
* Convert string to title case.
|
||||
*
|
||||
* @param {string} string - the string to convert
|
||||
* @return {string} the returned titlecase string
|
||||
*/
|
||||
function toTitleCase(string) {
|
||||
if (typeof string !== 'string') {
|
||||
return string;
|
||||
}
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend vjs button class for quality button.
|
||||
*/
|
||||
class ConcreteButton extends MenuButton {
|
||||
/**
|
||||
* Button constructor.
|
||||
*
|
||||
* @param {Player} player - videojs player instance
|
||||
*/
|
||||
constructor(player) {
|
||||
super(player, {
|
||||
title: player.localize('Quality'),
|
||||
name: 'QualityButton'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates button items.
|
||||
*
|
||||
* @return {Array} - Button items
|
||||
*/
|
||||
createItems() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the menu and add all items to it.
|
||||
*
|
||||
* @return {Menu}
|
||||
* The constructed menu
|
||||
*/
|
||||
createMenu() {
|
||||
const menu = new Menu(this.player_, {
|
||||
menuButton: this
|
||||
});
|
||||
this.hideThreshold_ = 0;
|
||||
|
||||
this.items = this.createItems();
|
||||
if (this.items) {
|
||||
// Add menu items to the menu
|
||||
for (let i = 0; i < this.items.length; i++) {
|
||||
menu.addItem(this.items[i]);
|
||||
}
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
|
||||
// Concrete classes
|
||||
const VideoJsMenuItemClass = videojs__default['default'].getComponent('MenuItem');
|
||||
|
||||
/**
|
||||
* Extend vjs menu item class.
|
||||
*/
|
||||
class ConcreteMenuItem extends VideoJsMenuItemClass {
|
||||
/**
|
||||
* Menu item constructor.
|
||||
*
|
||||
* @param {Player} player - vjs player
|
||||
* @param {Object} item - Item object
|
||||
* @param {ConcreteButton} qualityButton - The containing button.
|
||||
* @param {HlsQualitySelector} plugin - This plugin instance.
|
||||
*/
|
||||
constructor(player, item, qualityButton, plugin) {
|
||||
super(player, {
|
||||
label: item.label,
|
||||
selectable: true,
|
||||
selected: item.selected || false
|
||||
});
|
||||
this.item = item;
|
||||
this.qualityButton = qualityButton;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Click event for menu item.
|
||||
*/
|
||||
handleClick() {
|
||||
// Reset other menu items selected status.
|
||||
for (let i = 0; i < this.qualityButton.items.length; ++i) {
|
||||
this.qualityButton.items[i].selected(false);
|
||||
}
|
||||
|
||||
// Set this menu item to selected, and set quality.
|
||||
this.plugin.setQuality(this.item.value);
|
||||
this.selected(true);
|
||||
}
|
||||
}
|
||||
|
||||
const Plugin = videojs__default['default'].getPlugin('plugin');
|
||||
|
||||
// Default options for the plugin.
|
||||
const defaults = {};
|
||||
|
||||
/**
|
||||
* An advanced Video.js plugin. For more information on the API
|
||||
*
|
||||
* See: https://blog.videojs.com/feature-spotlight-advanced-plugins/
|
||||
*/
|
||||
class HlsQualitySelector extends Plugin {
|
||||
/**
|
||||
* Create a HlsQualitySelector plugin instance.
|
||||
*
|
||||
* @param {Player} player
|
||||
* A Video.js Player instance.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* An optional options object.
|
||||
*
|
||||
* While not a core part of the Video.js plugin architecture, a
|
||||
* second argument of options is a convenient way to accept inputs
|
||||
* from your plugin's caller.
|
||||
*/
|
||||
constructor(player, options) {
|
||||
// the parent class will add player under this.player
|
||||
super(player);
|
||||
this.options = videojs__default['default'].obj.merge(defaults, options);
|
||||
this.player.ready(() => {
|
||||
// If there is quality levels plugin and the HLS tech exists then continue.
|
||||
if (this.player.qualityLevels) {
|
||||
this.player.addClass('vjs-hls-quality-selector');
|
||||
// Create the quality button.
|
||||
this.createQualityButton();
|
||||
this.bindPlayerEvents();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds listener for quality level changes.
|
||||
*/
|
||||
bindPlayerEvents() {
|
||||
this.player.qualityLevels().on('addqualitylevel', this.onAddQualityLevel.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the quality menu button to the player control bar.
|
||||
*/
|
||||
createQualityButton() {
|
||||
const player = this.player;
|
||||
this._qualityButton = new ConcreteButton(player);
|
||||
const placementIndex = player.controlBar.children().length - 2;
|
||||
const concreteButtonInstance = player.controlBar.addChild(this._qualityButton, {
|
||||
componentClass: 'qualitySelector'
|
||||
}, this.options.placementIndex || placementIndex);
|
||||
concreteButtonInstance.addClass('vjs-quality-selector');
|
||||
if (!this.options.displayCurrentQuality) {
|
||||
const icon = ` ${this.options.vjsIconClass || 'vjs-icon-hd'}`;
|
||||
concreteButtonInstance.menuButton_.$('.vjs-icon-placeholder').className += icon;
|
||||
} else {
|
||||
this.setButtonInnerText(player.localize('Auto'));
|
||||
}
|
||||
concreteButtonInstance.removeClass('vjs-hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
*Set inner button text.
|
||||
*
|
||||
* @param {string} text - the text to display in the button.
|
||||
*/
|
||||
setButtonInnerText(text) {
|
||||
this._qualityButton.menuButton_.$('.vjs-icon-placeholder').innerHTML = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds individual quality menu items.
|
||||
*
|
||||
* @param {Object} item - Individual quality menu item.
|
||||
* @return {ConcreteMenuItem} - Menu item
|
||||
*/
|
||||
getQualityMenuItem(item) {
|
||||
const player = this.player;
|
||||
return new ConcreteMenuItem(player, item, this._qualityButton, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed when a quality level is added from HLS playlist.
|
||||
*/
|
||||
onAddQualityLevel() {
|
||||
const player = this.player;
|
||||
const qualityList = player.qualityLevels();
|
||||
const levels = qualityList.levels_ || [];
|
||||
const levelItems = [];
|
||||
for (let i = 0; i < levels.length; ++i) {
|
||||
const {
|
||||
width,
|
||||
height
|
||||
} = levels[i];
|
||||
const pixels = width > height ? height : width;
|
||||
if (!pixels) {
|
||||
continue;
|
||||
}
|
||||
if (!levelItems.filter(_existingItem => {
|
||||
return _existingItem.item && _existingItem.item.value === pixels;
|
||||
}).length) {
|
||||
const levelItem = this.getQualityMenuItem.call(this, {
|
||||
label: pixels + 'p',
|
||||
value: pixels
|
||||
});
|
||||
levelItems.push(levelItem);
|
||||
}
|
||||
}
|
||||
levelItems.sort((current, next) => {
|
||||
if (typeof current !== 'object' || typeof next !== 'object') {
|
||||
return -1;
|
||||
}
|
||||
if (current.item.value < next.item.value) {
|
||||
return 1;
|
||||
}
|
||||
if (current.item.value > next.item.value) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
levelItems.push(this.getQualityMenuItem.call(this, {
|
||||
label: this.player.localize('Auto'),
|
||||
value: 'auto',
|
||||
selected: true
|
||||
}));
|
||||
if (this._qualityButton) {
|
||||
this._qualityButton.createItems = () => {
|
||||
return levelItems;
|
||||
};
|
||||
this._qualityButton.update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets quality (based on media short side)
|
||||
*
|
||||
* @param {number} quality - A number representing HLS playlist.
|
||||
*/
|
||||
setQuality(quality) {
|
||||
const qualityList = this.player.qualityLevels();
|
||||
|
||||
// Set quality on plugin
|
||||
this._currentQuality = quality;
|
||||
if (this.options.displayCurrentQuality) {
|
||||
this.setButtonInnerText(quality === 'auto' ? this.player.localize('Auto') : `${quality}p`);
|
||||
}
|
||||
for (let i = 0; i < qualityList.length; ++i) {
|
||||
const {
|
||||
width,
|
||||
height
|
||||
} = qualityList[i];
|
||||
const pixels = width > height ? height : width;
|
||||
qualityList[i].enabled = pixels === quality || quality === 'auto';
|
||||
}
|
||||
|
||||
this._qualityButton.unpressButton();
|
||||
|
||||
// Select the correct quality menu item on screen
|
||||
for (let i = 0; i < this._qualityButton.items.length; ++i) {
|
||||
if (this._qualityButton.items[i]['item']['value'] == this._currentQuality) {
|
||||
this._qualityButton.items[i].selected(true);
|
||||
}
|
||||
else {
|
||||
this._qualityButton.items[i].selected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current set quality or 'auto'
|
||||
*
|
||||
* @return {string} the currently set quality
|
||||
*/
|
||||
getCurrentQuality() {
|
||||
return this._currentQuality || 'auto';
|
||||
}
|
||||
}
|
||||
|
||||
// Include the version number.
|
||||
HlsQualitySelector.VERSION = version;
|
||||
|
||||
// Register the plugin with video.js.
|
||||
videojs__default['default'].registerPlugin('hlsQualitySelector', HlsQualitySelector);
|
||||
|
||||
return HlsQualitySelector;
|
||||
|
||||
})));
|
||||
@@ -1,603 +0,0 @@
|
||||
/**
|
||||
* pthumbnails
|
||||
* @version 1.2.0
|
||||
* @copyright 2020 Rigel Kent <sendmemail@rigelk.eu>
|
||||
* @license MIT
|
||||
*/
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['video.js'], factory) :
|
||||
(global.videojsVttThumbnails = factory(global.videojs));
|
||||
}(this, (function (videojs) { 'use strict';
|
||||
|
||||
videojs = videojs && videojs.hasOwnProperty('default') ? videojs['default'] : videojs;
|
||||
|
||||
var version = "1.2.0";
|
||||
|
||||
var asyncGenerator = function () {
|
||||
function AwaitValue(value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
function AsyncGenerator(gen) {
|
||||
var front, back;
|
||||
|
||||
function send(key, arg) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var request = {
|
||||
key: key,
|
||||
arg: arg,
|
||||
resolve: resolve,
|
||||
reject: reject,
|
||||
next: null
|
||||
};
|
||||
|
||||
if (back) {
|
||||
back = back.next = request;
|
||||
} else {
|
||||
front = back = request;
|
||||
resume(key, arg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resume(key, arg) {
|
||||
try {
|
||||
var result = gen[key](arg);
|
||||
var value = result.value;
|
||||
|
||||
if (value instanceof AwaitValue) {
|
||||
Promise.resolve(value.value).then(function (arg) {
|
||||
resume("next", arg);
|
||||
}, function (arg) {
|
||||
resume("throw", arg);
|
||||
});
|
||||
} else {
|
||||
settle(result.done ? "return" : "normal", result.value);
|
||||
}
|
||||
} catch (err) {
|
||||
settle("throw", err);
|
||||
}
|
||||
}
|
||||
|
||||
function settle(type, value) {
|
||||
switch (type) {
|
||||
case "return":
|
||||
front.resolve({
|
||||
value: value,
|
||||
done: true
|
||||
});
|
||||
break;
|
||||
|
||||
case "throw":
|
||||
front.reject(value);
|
||||
break;
|
||||
|
||||
default:
|
||||
front.resolve({
|
||||
value: value,
|
||||
done: false
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
front = front.next;
|
||||
|
||||
if (front) {
|
||||
resume(front.key, front.arg);
|
||||
} else {
|
||||
back = null;
|
||||
}
|
||||
}
|
||||
|
||||
this._invoke = send;
|
||||
|
||||
if (typeof gen.return !== "function") {
|
||||
this.return = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof Symbol === "function" && Symbol.asyncIterator) {
|
||||
AsyncGenerator.prototype[Symbol.asyncIterator] = function () {
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
AsyncGenerator.prototype.next = function (arg) {
|
||||
return this._invoke("next", arg);
|
||||
};
|
||||
|
||||
AsyncGenerator.prototype.throw = function (arg) {
|
||||
return this._invoke("throw", arg);
|
||||
};
|
||||
|
||||
AsyncGenerator.prototype.return = function (arg) {
|
||||
return this._invoke("return", arg);
|
||||
};
|
||||
|
||||
return {
|
||||
wrap: function (fn) {
|
||||
return function () {
|
||||
return new AsyncGenerator(fn.apply(this, arguments));
|
||||
};
|
||||
},
|
||||
await: function (value) {
|
||||
return new AwaitValue(value);
|
||||
}
|
||||
};
|
||||
}();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var classCallCheck = function (instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
}
|
||||
};
|
||||
|
||||
// import request from 'request';
|
||||
|
||||
// Default options for the plugin.
|
||||
var defaults = {};
|
||||
|
||||
// Cache for image elements
|
||||
var cache = {};
|
||||
|
||||
// Cross-compatibility for Video.js 5 and 6.
|
||||
var registerPlugin = videojs.registerPlugin || videojs.plugin;
|
||||
// const dom = videojs.dom || videojs;
|
||||
|
||||
/**
|
||||
* Function to invoke when the player is ready.
|
||||
*
|
||||
* This is a great place for your plugin to initialize itself. When this
|
||||
* function is called, the player will have its DOM and child components
|
||||
* in place.
|
||||
*
|
||||
* @function onPlayerReady
|
||||
* @param {Player} player
|
||||
* A Video.js player object.
|
||||
*
|
||||
* @param {Object} [options={}]
|
||||
* A plain object containing options for the plugin.
|
||||
*/
|
||||
var onPlayerReady = function onPlayerReady(player, options) {
|
||||
player.addClass('vjs-vtt-thumbnails');
|
||||
player.vttThumbnails = new vttThumbnailsPlugin(player, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* A video.js plugin.
|
||||
*
|
||||
* In the plugin function, the value of `this` is a video.js `Player`
|
||||
* instance. You cannot rely on the player being in a "ready" state here,
|
||||
* depending on how the plugin is invoked. This may or may not be important
|
||||
* to you; if not, remove the wait for "ready"!
|
||||
*
|
||||
* @function vttThumbnails
|
||||
* @param {Object} [options={}]
|
||||
* An object of options left to the plugin author to define.
|
||||
*/
|
||||
var vttThumbnails = function vttThumbnails(options) {
|
||||
var _this = this;
|
||||
|
||||
this.ready(function () {
|
||||
onPlayerReady(_this, videojs.mergeOptions(defaults, options));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* VTT Thumbnails class.
|
||||
*
|
||||
* This class performs all functions related to displaying the vtt
|
||||
* thumbnails.
|
||||
*/
|
||||
|
||||
var vttThumbnailsPlugin = function () {
|
||||
|
||||
/**
|
||||
* Plugin class constructor, called by videojs on
|
||||
* ready event.
|
||||
*
|
||||
* @function constructor
|
||||
* @param {Player} player
|
||||
* A Video.js player object.
|
||||
*
|
||||
* @param {Object} [options={}]
|
||||
* A plain object containing options for the plugin.
|
||||
* - src: path to the .vtt file
|
||||
* - baseUrl (optional): host prepended to the image definitions
|
||||
* - preloadStrategy (optional): preload images in the cache, for smooth scrubbing (default: none, available: 'all')
|
||||
*/
|
||||
function vttThumbnailsPlugin(player, options) {
|
||||
classCallCheck(this, vttThumbnailsPlugin);
|
||||
|
||||
this.player = player;
|
||||
this.options = options;
|
||||
this.listenForDurationChange();
|
||||
this.initializeThumbnails();
|
||||
this.registeredEvents = {};
|
||||
return this;
|
||||
}
|
||||
|
||||
vttThumbnailsPlugin.prototype.src = function src(source) {
|
||||
this.resetPlugin();
|
||||
this.options.src = source;
|
||||
this.initializeThumbnails();
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.detach = function detach() {
|
||||
this.resetPlugin();
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.resetPlugin = function resetPlugin() {
|
||||
this.thumbnailHolder && this.thumbnailHolder.parentNode.removeChild(this.thumbnailHolder);
|
||||
this.progressBar && this.progressBar.removeEventListener('mouseenter', this.registeredEvents.progressBarMouseEnter);
|
||||
this.progressBar && this.progressBar.removeEventListener('mouseleave', this.registeredEvents.progressBarMouseLeave);
|
||||
this.progressBar && this.progressBar.removeEventListener('mousemove', this.registeredEvents.progressBarMouseMove);
|
||||
delete this.registeredEvents.progressBarMouseEnter;
|
||||
delete this.registeredEvents.progressBarMouseLeave;
|
||||
delete this.registeredEvents.progressBarMouseMove;
|
||||
delete this.progressBar;
|
||||
delete this.vttData;
|
||||
delete this.thumbnailHolder;
|
||||
delete this.timeHolder;
|
||||
delete this.lastStyle;
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.listenForDurationChange = function listenForDurationChange() {
|
||||
this.player.on('durationchange', function () {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Bootstrap the plugin.
|
||||
*/
|
||||
|
||||
|
||||
vttThumbnailsPlugin.prototype.initializeThumbnails = function initializeThumbnails() {
|
||||
var _this2 = this;
|
||||
|
||||
if (!this.options.src) {
|
||||
return;
|
||||
}
|
||||
var baseUrl = this.getBaseUrl();
|
||||
var url = this.getFullyQualifiedUrl(this.options.src, baseUrl);
|
||||
this.getVttFile(url).then(function (data) {
|
||||
_this2.vttData = _this2.processVtt(data);
|
||||
_this2.setupThumbnailElement();
|
||||
|
||||
if (_this2.options.hasOwnProperty('preloadStrategy')) {
|
||||
_this2.preload(_this2.vttData);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a base URL should we require one.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
|
||||
vttThumbnailsPlugin.prototype.getBaseUrl = function getBaseUrl() {
|
||||
return [window.location.protocol, '//', window.location.hostname, window.location.port ? ':' + window.location.port : '', window.location.pathname].join('').split(/([^\/]*)$/gi).shift();
|
||||
};
|
||||
|
||||
/**
|
||||
* Grabs the contents of the VTT file.
|
||||
*
|
||||
* @param url
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
|
||||
vttThumbnailsPlugin.prototype.getVttFile = function getVttFile(url) {
|
||||
var _this3 = this;
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.data = {
|
||||
resolve: resolve
|
||||
};
|
||||
req.addEventListener('load', _this3.vttFileLoaded);
|
||||
req.open('GET', url);
|
||||
req.send();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for loaded VTT file.
|
||||
*/
|
||||
|
||||
|
||||
vttThumbnailsPlugin.prototype.vttFileLoaded = function vttFileLoaded() {
|
||||
this.data.resolve(this.responseText);
|
||||
};
|
||||
|
||||
/**
|
||||
* This will fill the cache and thus preload images
|
||||
*/
|
||||
|
||||
|
||||
vttThumbnailsPlugin.prototype.preload = function preload(data) {
|
||||
var _this4 = this;
|
||||
|
||||
data.forEach(function (item) {
|
||||
return _this4.setImageInCacheForItem(item);
|
||||
});
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.setupThumbnailElement = function setupThumbnailElement(data) {
|
||||
var _this5 = this;
|
||||
|
||||
var mouseDisplay = this.player.$('.vjs-mouse-display');
|
||||
this.progressBar = this.player.$('.vjs-progress-control');
|
||||
// thumbnail element
|
||||
var thumbHolder = document.createElement('div');
|
||||
thumbHolder.setAttribute('class', 'vjs-vtt-thumbnail-display');
|
||||
this.progressBar.appendChild(thumbHolder);
|
||||
this.thumbnailHolder = thumbHolder;
|
||||
// time element
|
||||
var timeHolder = document.createElement('time');
|
||||
timeHolder.setAttribute('class', 'vjs-vtt-thumbnail-time');
|
||||
this.thumbnailHolder.appendChild(timeHolder);
|
||||
this.timeHolder = timeHolder;
|
||||
if (mouseDisplay) {
|
||||
mouseDisplay.classList.add('vjs-hidden');
|
||||
}
|
||||
this.registeredEvents.progressBarMouseEnter = function () {
|
||||
return _this5.onBarMouseenter();
|
||||
};
|
||||
this.registeredEvents.progressBarMouseLeave = function () {
|
||||
return _this5.onBarMouseleave();
|
||||
};
|
||||
this.progressBar.addEventListener('mouseenter', this.registeredEvents.progressBarMouseEnter);
|
||||
this.progressBar.addEventListener('mouseleave', this.registeredEvents.progressBarMouseLeave);
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.onBarMouseenter = function onBarMouseenter() {
|
||||
var _this6 = this;
|
||||
|
||||
this.mouseMoveCallback = function (e) {
|
||||
_this6.onBarMousemove(e);
|
||||
};
|
||||
this.registeredEvents.progressBarMouseMove = this.mouseMoveCallback;
|
||||
this.progressBar.addEventListener('mousemove', this.registeredEvents.progressBarMouseMove);
|
||||
this.showThumbnailHolder();
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.onBarMouseleave = function onBarMouseleave() {
|
||||
if (this.registeredEvents.progressBarMouseMove) {
|
||||
this.progressBar.removeEventListener('mousemove', this.registeredEvents.progressBarMouseMove);
|
||||
}
|
||||
this.hideThumbnailHolder();
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.getXCoord = function getXCoord(bar, mouseX) {
|
||||
var rect = bar.getBoundingClientRect();
|
||||
var docEl = document.documentElement;
|
||||
return mouseX - (rect.left + (window.pageXOffset || docEl.scrollLeft || 0));
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.onBarMousemove = function onBarMousemove(event) {
|
||||
this.updateThumbnailStyle(videojs.dom.getPointerPosition(this.progressBar, event).x, this.progressBar.offsetWidth);
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.setImageInCacheForItem = function setImageInCacheForItem(item) {
|
||||
// Cache miss
|
||||
if (item.css.url && !cache[item.css.url]) {
|
||||
var image = new Image();
|
||||
image.src = item.css.url;
|
||||
cache[item.css.url] = image;
|
||||
}
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.getStyleForTime = function getStyleForTime(time) {
|
||||
for (var i = 0; i < this.vttData.length; ++i) {
|
||||
var item = this.vttData[i];
|
||||
if (time >= item.start && time < item.end) {
|
||||
this.setImageInCacheForItem(item);
|
||||
|
||||
return item.css;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.showThumbnailHolder = function showThumbnailHolder() {
|
||||
this.thumbnailHolder.style.opacity = '1';
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.hideThumbnailHolder = function hideThumbnailHolder() {
|
||||
this.thumbnailHolder.style.opacity = '0';
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.updateThumbnailStyle = function updateThumbnailStyle(percent, width) {
|
||||
var duration = this.player.duration();
|
||||
var time = percent * duration;
|
||||
var currentStyle = this.getStyleForTime(time);
|
||||
|
||||
var timestamp = new Date(Math.round(time) * 1000).toISOString().substr(11, 8);
|
||||
timestamp = duration > 3599 ? timestamp : timestamp.substring(3);
|
||||
this.timeHolder.innerText = timestamp;
|
||||
|
||||
if (!currentStyle) {
|
||||
return this.hideThumbnailHolder();
|
||||
}
|
||||
|
||||
var xPos = percent * width;
|
||||
var thumbnailWidth = parseInt(this.thumbnailHolder.offsetWidth);
|
||||
var halfthumbnailWidth = thumbnailWidth / 2;
|
||||
var marginRight = width - (xPos + halfthumbnailWidth);
|
||||
var marginLeft = xPos - halfthumbnailWidth;
|
||||
|
||||
if (marginLeft > 0 && marginRight > 0) {
|
||||
this.thumbnailHolder.style.transform = 'translateX(' + (xPos - halfthumbnailWidth) + 'px)';
|
||||
} else if (marginLeft <= 0) {
|
||||
this.thumbnailHolder.style.transform = 'translateX(' + 0 + 'px)';
|
||||
} else if (marginRight <= 0) {
|
||||
this.thumbnailHolder.style.transform = 'translateX(' + (xPos + marginRight - halfthumbnailWidth) + 'px)';
|
||||
}
|
||||
|
||||
if (this.lastStyle && this.lastStyle === currentStyle) {
|
||||
return;
|
||||
}
|
||||
this.lastStyle = currentStyle;
|
||||
|
||||
for (var style in currentStyle) {
|
||||
if (currentStyle.hasOwnProperty(style)) {
|
||||
this.thumbnailHolder.style[style] = currentStyle[style];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.processVtt = function processVtt(data) {
|
||||
var _this7 = this;
|
||||
|
||||
var processedVtts = [];
|
||||
var vttDefinitions = data.split(/[\r\n][\r\n]/i);
|
||||
vttDefinitions.forEach(function (vttDef) {
|
||||
if (vttDef.match(/([0-9]{2}:)?([0-9]{2}:)?[0-9]{2}(.[0-9]{3})?( ?--> ?)([0-9]{2}:)?([0-9]{2}:)?[0-9]{2}(.[0-9]{3})?[\r\n]{1}.*/gi)) {
|
||||
var vttDefSplit = vttDef.split(/[\r\n]/i);
|
||||
var vttTiming = vttDefSplit[0];
|
||||
var vttTimingSplit = vttTiming.split(/ ?--> ?/i);
|
||||
var vttTimeStart = vttTimingSplit[0];
|
||||
var vttTimeEnd = vttTimingSplit[1];
|
||||
var vttImageDef = vttDefSplit[1];
|
||||
var vttCssDef = _this7.getVttCss(vttImageDef);
|
||||
|
||||
processedVtts.push({
|
||||
start: _this7.getSecondsFromTimestamp(vttTimeStart),
|
||||
end: _this7.getSecondsFromTimestamp(vttTimeEnd),
|
||||
css: vttCssDef
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return processedVtts;
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.getFullyQualifiedUrl = function getFullyQualifiedUrl(path, base) {
|
||||
if (path.indexOf('//') >= 0) {
|
||||
// We have a fully qualified path.
|
||||
return path;
|
||||
}
|
||||
if (base.indexOf('//') === 0) {
|
||||
// We don't have a fully qualified path, but need to
|
||||
// be careful with trimming.
|
||||
return [base.replace(/\/$/gi, ''), this.trim(path, '/')].join('/');
|
||||
}
|
||||
if (base.indexOf('//') > 0) {
|
||||
// We don't have a fully qualified path, and should
|
||||
// trim both sides of base and path.
|
||||
return [this.trim(base, '/'), this.trim(path, '/')].join('/');
|
||||
}
|
||||
|
||||
// If all else fails.
|
||||
return path;
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.getPropsFromDef = function getPropsFromDef(def) {
|
||||
var imageDefSplit = def.split(/#xywh=/i);
|
||||
var imageUrl = imageDefSplit[0];
|
||||
var imageCoords = imageDefSplit[1];
|
||||
var splitCoords = imageCoords.match(/[0-9]+/gi);
|
||||
return {
|
||||
x: splitCoords[0],
|
||||
y: splitCoords[1],
|
||||
w: splitCoords[2],
|
||||
h: splitCoords[3],
|
||||
image: imageUrl
|
||||
};
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.getVttCss = function getVttCss(vttImageDef) {
|
||||
|
||||
var cssObj = {};
|
||||
|
||||
// If there isn't a protocol, use the VTT source URL.
|
||||
var baseSplit = void 0;
|
||||
if (this.options.baseUrl) {
|
||||
baseSplit = this.options.baseUrl;
|
||||
} else if (this.options.src.indexOf('//') >= 0) {
|
||||
baseSplit = this.options.src.split(/([^\/]*)$/gi).shift();
|
||||
} else {
|
||||
baseSplit = this.getBaseUrl() + this.options.src.split(/([^\/]*)$/gi).shift();
|
||||
}
|
||||
|
||||
vttImageDef = this.getFullyQualifiedUrl(vttImageDef, baseSplit);
|
||||
|
||||
// deal with regular thumbnails
|
||||
if (!vttImageDef.match(/#xywh=/i)) {
|
||||
cssObj.background = 'url("' + vttImageDef + '")';
|
||||
cssObj.url = vttImageDef;
|
||||
return cssObj;
|
||||
}
|
||||
|
||||
// deal with sprited thumbnails
|
||||
var imageProps = this.getPropsFromDef(vttImageDef);
|
||||
cssObj.background = 'url("' + imageProps.image + '") no-repeat -' + imageProps.x + 'px -' + imageProps.y + 'px';
|
||||
cssObj.width = imageProps.w + 'px';
|
||||
cssObj.height = imageProps.h + 'px';
|
||||
cssObj.url = imageProps.image;
|
||||
|
||||
return cssObj;
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.doconstructTimestamp = function doconstructTimestamp(timestamp) {
|
||||
var splitStampMilliseconds = timestamp.split('.');
|
||||
var timeParts = splitStampMilliseconds[0];
|
||||
var timePartsSplit = timeParts.split(':');
|
||||
return {
|
||||
milliseconds: parseInt(splitStampMilliseconds[1]) || 0,
|
||||
seconds: parseInt(timePartsSplit.pop()) || 0,
|
||||
minutes: parseInt(timePartsSplit.pop()) || 0,
|
||||
hours: parseInt(timePartsSplit.pop()) || 0
|
||||
};
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.getSecondsFromTimestamp = function getSecondsFromTimestamp(timestamp) {
|
||||
var timestampParts = this.doconstructTimestamp(timestamp);
|
||||
return parseInt(timestampParts.hours * (60 * 60) + timestampParts.minutes * 60 + timestampParts.seconds + timestampParts.milliseconds / 1000);
|
||||
};
|
||||
|
||||
vttThumbnailsPlugin.prototype.trim = function trim(str, charlist) {
|
||||
var whitespace = [' ', '\n', '\r', '\t', '\f', '\x0b', '\xa0', '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006', '\u2007', '\u2008', '\u2009', '\u200A', '\u200B', '\u2028', '\u2029', '\u3000'].join('');
|
||||
var l = 0;
|
||||
var i = 0;
|
||||
str += '';
|
||||
if (charlist) {
|
||||
whitespace = (charlist + '').replace(/([[\]().?/*{}+$^:])/g, '$1');
|
||||
}
|
||||
l = str.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
if (whitespace.indexOf(str.charAt(i)) === -1) {
|
||||
str = str.substring(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
l = str.length;
|
||||
for (i = l - 1; i >= 0; i--) {
|
||||
if (whitespace.indexOf(str.charAt(i)) === -1) {
|
||||
str = str.substring(0, i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
|
||||
};
|
||||
|
||||
return vttThumbnailsPlugin;
|
||||
}();
|
||||
|
||||
// Register the plugin with video.js.
|
||||
|
||||
|
||||
registerPlugin('vttThumbnails', vttThumbnails);
|
||||
|
||||
// Include the version number.
|
||||
vttThumbnails.VERSION = version;
|
||||
|
||||
return vttThumbnails;
|
||||
|
||||
})));
|
||||