Merge pull request #75 from ekimekim/gunner/thrimbletrimmer-integration

Gunner/thrimbletrimmer integration
mike/render-dashboard
MasterGunner 5 years ago committed by GitHub
commit 54f6a707f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -64,6 +64,8 @@
authentication:: true, // set to false to disable auth in thrimshim
thrimbletrimmer:: true, // set to false to not have nginx serve thrimbletrimmer pages.
// Connection args for the database.
// If database is defined in this config, host and port should be postgres:5432.
db_args:: {
@ -86,7 +88,7 @@
google_creds:: "./google_creds.json",
// The URL to write to the sheet for edit links, with {} being replaced by the id
edit_url:: "http://thrimbletrimmer.codegunner.com/{}",
edit_url:: "http://thrimbletrimmer.codegunner.com/?id={}",
// The timestamp corresponding to 00:00 in bustime
bustime_start:: "1970-01-01T00:00:00Z",
@ -189,6 +191,8 @@
command: [
"--backdoor-port", std.toString($.backdoor_port),
$.db_connect,
$.channel,
$.bustime_start,
] + if $.authentication then [] else ["--no-authentication"],
// Mount the segments directory at /mnt
volumes: ["%s:/mnt" % $.segments_path],
@ -240,6 +244,7 @@
for service in std.objectFields(forward_ports)
if service in $.enabled && $.enabled[service]
]),
THRIMBLETRIMMER: if $.thrimbletrimmer then "true" else "",
},
},

@ -1,4 +1,5 @@
# nginx container contains config that exposes all the various services metrics
FROM nginx:latest
ADD nginx/generate-config /
COPY thrimbletrimmer /etc/nginx/html/thrimbletrimmer
ENTRYPOINT ["/bin/sh", "-c", "/generate-config && nginx -g \"daemon off;\""]

@ -14,9 +14,10 @@ LOCATIONS=$(
[ "$name" == "restreamer" ] && generate_location / "http://restreamer:$port"
# thrimshim takes any calls to thrimshim/
[ "$name" == "thrimshim" ] && generate_location /thrimshim "http://thrimshim:$port"
# all services have metrics under /metrics/SERVICE
# all services have metrics under /metrics/SERVICE, except for thrimebletrimmer
generate_location "/metrics/$name" "http://$name:$port/metrics"
done
[ -n "$THRIMBLETRIMMER" ] && echo -e "\t\tlocation = / { return 301 \$scheme://\$host/thrimbletrimmer/dashboard.html; }\n\t\tlocation /thrimbletrimmer { }"
)
cat > /etc/nginx/nginx.conf <<EOF
@ -27,8 +28,11 @@ events {
}
http {
include /etc/nginx/mime.types;
server {
listen 80;
gzip on;
gzip_comp_level 9;
$LOCATIONS
}
}

@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Thrimbletrimmer Goes Forth</title>
<meta name="description" content="">
<meta name="google-signin-client_id" content="345276493482-r84m2giavk10glnmqna0lbq8e1hdaus0.apps.googleusercontent.com">
<script src="https://apis.google.com/js/platform.js?onload=onGLoad" async defer></script>
</head>
<body>
<!-- https://developers.google.com/identity/sign-in/web/sign-in -->
<div class="g-signin2" data-onsuccess="onSignIn"></div>
<a href="#" onclick="signOut();">Sign out</a>
<script>
var user;
function onSignIn(googleUser) {
user = googleUser;
var profile = googleUser.getBasicProfile();
console.log('ID: ' + profile.getId()); // Do not send to your backend! Use an ID token instead.
console.log('Name: ' + profile.getName());
console.log('Image URL: ' + profile.getImageUrl());
console.log('Email: ' + profile.getEmail()); // This is null if the 'email' scope is not present.
console.log('ID Token: ' + googleUser.getAuthResponse().id_token);
}
function signOut() {
user = null;
var auth2 = gapi.auth2.getAuthInstance();
auth2.signOut().then(function () {
console.log('User signed out.');
});
}
</script>
<div>
<br/>
<input id="server" value="" />
<input id="rowId" value="04860aa4-d6a5-11e9-9d36-2a2ae2dbcce4" />
<input type="button" onclick="getRow()" value="Get Row"/>
<a href="/thrimshim">All Rows</a>
<br/>
<textarea id="rowdump"></textarea>
</div>
<div>
<br/>
<input type="button" onclick="testAuth()" value="Test Auth" /><br />
<input type="button" onclick="testSubmit()" value="Test Submit" /><br />
<input type="button" onclick="testManualLink()" value="Test Manual Link" /><input id="manualLink" value="google.ca"/><br />
<input type="button" onclick="testReset()" value="Test Reset" /><br />
</div>
<script>
function getRow() {
fetch(document.getElementById("server").value+"/thrimshim/"+document.getElementById("rowId").value).then(function (response) {
response.text().then(function(data) {
document.getElementById("rowdump").value=data;
});
});
}
function testAuth() {
fetch(document.getElementById("server").value+"/thrimshim/auth-test", {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
test: "123 Hello World?",
token: user.getAuthResponse().id_token
})
}).then(function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' + response.status);
return;
}
// Examine the text in the response
response.json().then(function(data) {
console.log(data);
});
}).catch(function(err) {
console.log('Fetch Error :-S', err);
});
}
function testSubmit() {
var wubData = {
allow_holes: "true",
experimental: "true",
state: "EDITED",
token: user.getAuthResponse().id_token,
upload_location: "YouTube",
uploader_whitelist: null,
video_channel: "rpglimitbreak",
video_description: "Test Description",
video_end: "2019-09-14T03:54:28.546",
video_quality: "source",
video_start: "2019-09-14T03:48:06.546",
video_title: "Test"
}
//Submit to thrimshim
fetch(document.getElementById("server").value+"/thrimshim/"+document.getElementById("rowId").value, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(wubData)
}).then(data => console.log(data));
}
function testManualLink() {
fetch(document.getElementById("server").value+"/thrimshim/manual-link/"+document.getElementById("rowId").value, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
link: document.getElementById("manualLink").value,
token: user.getAuthResponse().id_token
})
}).then(data => console.log(data));
}
function testReset() {
fetch(document.getElementById("server").value+"/thrimshim/reset/"+document.getElementById("rowId").value, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({token: user.getAuthResponse().id_token})
}).then(data => console.log(data));
}
</script>
</body>
</html>

@ -0,0 +1,247 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Thrimbletrimmer Goes Forth</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script> -->
<style>
html, body {
height:100%;
margin:0px;
background-color:darkgrey;
}
.sectionContainer {
margin:24px auto;
padding:32px;
border-radius:4px;
background:white;
box-shadow:0 0 34px rgba(0,0,0,.26);
max-width:1280px;
}
#QueueTable {
width:100%;
border-collapse: collapse;
text-align: center;
}
#QueueTable, #QueueTable th, #QueueTable td {
border: 1px solid #cccccc;
}
#QueueTable tr:nth-child(even) {
background-color: #f2f2f2;
}
#QueueTable tr:hover {
background-color:#dddddd;
}
#QueueTable th {
line-height:32px;
}
.filterMenu {
display: inline-block;
padding: 10px;
background-color: rgb(221, 221, 221);
border-radius: 5px 5px 0px 0px;
}
.filterList {
display:none;
List-style-type:none;
margin:10px 0px 0px -10px;
padding:10px;
border:1px solid black;
position:absolute;
background-color:white;
min-width:100px;
}
.filterMenu:hover .filterList {
display:inline-block;
}
</style>
<style>
/* Styles based on event category */
#QueueTable .RDP {
background-color: #f4cccc;
}
</style>
</head>
<body>
<div>
<!-- <div class="sectionContainer" style="display:none;">
<table>
<tr>
<th>Stream</th>
<th>Start Time</th>
<th>End Time</th>
<th></th>
</tr>
<tr>
<td><input id="StreamName" value="gamesdonequick" /></td>
<td><input id="StreamStart" value="2019-01-06T16:00:00" /></td>
<td><input id="StreamEnd" value="2019-01-06T17:00:00" /></td>
<td><input type="button" value="Load Playlist" onclick="loadPlaylist()" /></td>
</tr>
<tr>
<td><a href="javascript:alert('seabats, lunarjade, gamesdonequick');">Streams</a></td>
<td><a href="javascript:window.open('/files/' + document.getElementById('StreamName').value + '/source', '_blank');">Hours</a></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</div> -->
<div class="sectionContainer">
<h1 style="color:#1976d2;font-size:34px;line-height:38px;">Wubloader Queue</h1>
<!-- Add in filters based on time period, category, and state -->
<!-- Throw in JQueryUI to run the filter menus, not worth re-inventing the wheel -->
<style>
.ui-menu { width: 150px; }
</style>
<div id="FiltersMenu">
<div class="filterMenu" style="padding-bottom:7px;">
<input id="DateFilterStart" placeholder="Start Date" />
</div>
<div class="filterMenu" style="padding-bottom:7px;">
<input id="DateFilterEnd" placeholder="End Date" />
</div>
<div id="CategoryFiltersMenu" class="filterMenu">
<div>Category Filter</div>
<ul id="CategoryFilter" class="filterList"></ul>
</div>
<div id="StateFiltersMenu" class="filterMenu">
<div>State Filter</div>
<ul id="StateFilter" class="filterList"></ul>
</div>
<input type="button" value="Apply Filters" onclick="applyFilters()"/>
<input type="button" value="Reset Filters" onclick="window.location.href = '/thrimbletrimmer/dashboard.html'"/>
</div>
<table id="QueueTable">
<tr>
<th>Start Time</th>
<th>End Time</th>
<th>Category</th>
<th>Description</th>
<th>State</th>
<th>Edit</th>
<th>Link</th>
</tr>
</table>
</div>
</div>
<script>
// var startOfHour = new Date(new Date().setMinutes(0,0,0));
// document.getElementById("StreamStart").value = new Date(startOfHour.getTime() - 1000*60*60).toISOString().substring(0,19);
// document.getElementById("StreamEnd").value = startOfHour.toISOString().substring(0,19);
function sanitizeInput(input) {
let tempElement = document.createElement("SPAN");
tempElement.appendChild(document.createTextNode(input));
return tempElement.innerHTML;
}
async function getEvents() {
let response = await fetch("/thrimshim");
let data = await response.json();
//Sanitize user inputs that could be used for xss
data.forEach(event => {
event.category = sanitizeInput(event.category);
event.description = sanitizeInput(event.description);
})
return data;
}
function populateFilters(events) {
var urlParams = new URLSearchParams(window.location.search);
//Set up Date filters
if (urlParams.has("start")) {
document.getElementById("DateFilterStart").value = urlParams.get("start");
}
if (urlParams.has("end")) {
document.getElementById("DateFilterEnd").value = urlParams.get("end");
}
//Set up Category filters
let categoryFilters = urlParams.has("category") ? urlParams.get("category").split(","):null;
new Set(events.map(event => event.category).sort()).forEach((category, index) => {
let row = document.createElement("TR");
row.innerHTML = `<li><input type="checkbox" name="categoryCheckbox-${index}" filtervalue="${category}" ${!categoryFilters || categoryFilters.indexOf(category) >=0 ? "checked":""}><label for="categoryCheckbox-${index}">${category}</label></li>`;
document.getElementById('CategoryFilter').appendChild(row);
});
//Set up State filters
let stateFilters = urlParams.has("state") ? urlParams.get("state").split(","):null;
//new Set(events.map(event => event.state).sort()).forEach((state, index) => {
["UNEDITED", "EDITED", "CLAIMED", "FINALIZING", "TRANSCODING", "DONE"].forEach((state, index) => {
let row = document.createElement("TR");
row.innerHTML = `<li><input type="checkbox" name="stateCheckbox-${index}" filtervalue="${state}" ${!stateFilters || stateFilters.indexOf(state) >=0 ? "checked":""}><label for="stateCheckbox-${index}">${state}</label></li>`;
document.getElementById('StateFilter').appendChild(row);
});
return events;
}
function filterEvents(events) {
if(!window.location.search) { return events; }
var urlParams = new URLSearchParams(window.location.search);
if(urlParams.has("start")) {
let startDate = new Date(urlParams.get("start"));
events = events.filter(event => new Date(event.event_start) >= startDate);
}
if(urlParams.has("end")) {
let endDate = new Date(urlParams.get("end"));
events = events.filter(event => new Date(event.event_start) <= endDate);
}
if(urlParams.has("category")) {
events = events.filter(event => urlParams.get("category").split(",").indexOf(event.category) >= 0);
}
if(urlParams.has("state")) {
events = events.filter(event => urlParams.get("state").split(",").indexOf(event.state) >= 0);
}
return events;
}
function populateTable (events) {
events.forEach(event => {
let row = document.createElement("TR");
row.innerHTML = `
<td>${event.event_start}</td>
<td>${event.event_end}</td>
<td class="${event.category.replace(/ /g, "")}">${event.category}</td>
<td>${event.description}</td>
<td>${event.state}</td>
<td><a href="/thrimbletrimmer?id=${event.id}">Edit</a></td>
<td>${event.video_link ? "<a href='"+event.video_link+"'>"+event.video_link+"</a>":""}</td>
`;
document.getElementById('QueueTable').appendChild(row);
});
}
function applyFilters() {
let startDateFilter = document.getElementById("DateFilterStart").value;
let endDateFilter = document.getElementById("DateFilterEnd").value;
let categoryfilters = Array.from(document.querySelectorAll("#CategoryFilter :checked")).map(checkbox => checkbox.getAttribute("filtervalue")).join();
let statefilters = Array.from(document.querySelectorAll("#StateFilter :checked")).map(checkbox => checkbox.getAttribute("filtervalue")).join();
window.location.href = `/thrimbletrimmer/dashboard.html?${startDateFilter ? "start="+startDateFilter+"&":""}${endDateFilter ? "end="+endDateFilter+"&":""}category=${categoryfilters}&state=${statefilters}`;
}
//On Page Load
getEvents().then(populateFilters).then(filterEvents).then(populateTable);
</script>
</body>
</html>

@ -0,0 +1,163 @@
<!--
TODOs:
Move Google sign-in/out buttons, and make it clear when you're not signed in.
Move "Reset" and "Manual Link" options in here.
Clean up the Options/Input header, move parts of it into a hidden "Advanced" menu.
Create a cleaner player-only version of the page, or find a way to make it a version of this one via URL switch.
-->
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Thrimbletrimmer Goes Forth</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="google-signin-client_id" content="345276493482-r84m2giavk10glnmqna0lbq8e1hdaus0.apps.googleusercontent.com">
<link href="/thrimbletrimmer/plugins/video.js/dist/video-js.min.css" rel="stylesheet">
<link href="/thrimbletrimmer/plugins/videojs-hls-quality-selector/dist/videojs-hls-quality-selector.css" rel="stylesheet">
<link href="/thrimbletrimmer/plugins/videojs-trimming-controls/dist/videojs-trimming-controls.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script> -->
<script src="/thrimbletrimmer/plugins/video.js/dist/video.min.js"></script>
<script src="/thrimbletrimmer/plugins/videojs-contrib-quality-levels/dist/videojs-contrib-quality-levels.min.js"></script>
<script src="/thrimbletrimmer/plugins/videojs-hls-quality-selector/dist/videojs-hls-quality-selector.min.js"></script>
<script src="/thrimbletrimmer/plugins/videojs-trimming-controls/dist/videojs-trimming-controls.js"></script>
<script src="https://apis.google.com/js/platform.js?onload=onGLoad" async defer></script>
<link href="/thrimbletrimmer/styles/style.css" rel="stylesheet">
</head>
<body>
<div style="max-width:1280px;margin:auto;padding-top:25px;">
<table id="wubloaderInputTable">
<tr>
<th>Stream</th>
<th>Start Time</th>
<th>End Time</th>
<th></th>
</tr>
<tr>
<td><input id="StreamName" value="desertbus" /></td>
<td><input id="StreamStart" style="display:none;" class="UTCTimeInput" value="" /><input id="BusTimeStart" class="BusTimeInput" value="0:00" /></td>
<td><input id="StreamEnd" style="display:none;" class="UTCTimeInput" value="" /><input id="BusTimeEnd" class="BusTimeInput" value="1:00" /></td>
<td><input type="button" value="Load Playlist" onclick="loadPlaylist()" /></td>
</tr>
<tr>
<td></td>
<td></td>
<td>
<input type="radio" id="BusTimeToggleUTC" name="BusTimeToggle" value="UTC" onclick="toggleTimeInput(this.value)"> UTC
<input type="radio" id="BusTimeToggleBus" name="BusTimeToggle" value="BUSTIME" onclick="toggleTimeInput(this.value)" checked="checked"> Bustime
</td>
<td><a id="AdvancedOptionsButton" href="JavaScript:toggleHiddenPane('wubloaderAdvancedInputTable');">Advanced Submit Options</a></td>
</tr>
</table>
<table id="wubloaderAdvancedInputTable" style="display:none;">
<tr>
<td><a href="javascript:window.open('/files');">Streams</a></td>
<td><a href="javascript:window.open('/files/' + document.getElementById('StreamName').value + '/source', '_blank');">Hours</a></td>
</tr>
<tr><td>Allow Holes: </td><td><input id="AllowHoles" type="checkbox" checked /></td></tr>
<tr><td>Quality Level: </td><td><select id="qualityLevel"></select></td></tr>
<tr><td>Upload Location: </td><td><select id="uploadLocation"><option value="YouTube" selected>YouTube</option></select></td></tr>
<tr><td>Uploader Whitelist: </td><td><input id="uploaderWhitelist" title="Uploader Whitelist" /></td></tr>
<tr>
<td>ThrimShim ID:</td>
<td><input id="hiddenSubmissionID" value="" /></td>
<td><input type="button" value="Load Event" onclick="window.location.search = '?id='+document.getElementById('hiddenSubmissionID').value"/></td>
</tr>
</table>
<div id="EditorContainer">
<video id="my-player" class="video-js" controls preload="auto" style="display:none">
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
</video>
</div>
<div id="EditorDetailsPane">
<div>
Title: <br />
<input type="text" id="VideoTitle" value="DB2019 - " maxlength="91" />
</div>
<div>
Description:<br/>
<textarea id="VideoDescription" ></textarea>
</div>
<input type="button" id="SubmitButton" value="Submit" onclick="thrimbletrimmerSubmit()"/>
<input type="button" id="DownloadButton" value="Download" onclick="thrimbletrimmerDownload()"/>
<a href="/thrimbletrimmer/dashboard.html">Go To Dashboard</a> |
<a id="ManualLinkButton" href="JavaScript:toggleHiddenPane('ManualLinkPane');">Manual Link</a> |
<a id="ResetButton" href="JavaScript:thrimbletrimmerResetLink();">Reset Edits</a>
<a id="HelpButton" style="float:right;" href="JavaScript:toggleHiddenPane('HelpPane');">Help</a>
<a id="UltrawideButton" style="float:right;margin-right:10px;" href="JavaScript:toggleUltrawide();">Ultrawide</a>
</div>
<div id="ManualLinkPane" style="display:none">
<input id="ManualLink" /> <input type="button" onclick="thrimbletrimmerManualLink()" value="Set Link" />
</div>
<div id="HelpPane" style="display:none;">
<ul>
<li>J/K/L - Back 10 seconds, Play/Pause, Advance 10 seconds</li>
<li>LeftArrow/RightArrow - Back 5 seconds, Advance 5 seconds</li>
<li>,/. - Back 0.1 seconds, Advance 0.1 seconds</li>
<li>I/O - Set start of trim at playhead, set end of trim at playhead</li>
<li>0-9 - Jump to 0% - 90% through the video.</li>
</ul>
</div>
</div>
<iframe id="outputFile" style="display:none;"></iframe>
<script src="/thrimbletrimmer/scripts/playerSetup.js"></script>
<script src="/thrimbletrimmer/scripts/keyboardShortcuts.js"></script>
<script src="/thrimbletrimmer/scripts/IO.js"></script>
<script>pageSetup();</script>
<script>
function toggleHiddenPane(paneID) {
var pane = document.getElementById(paneID);
pane.style.display = (pane.style.display === "none") ? "block":"none";
}
function toggleUltrawide() {
var body = document.getElementsByTagName("Body")[0];
body.classList.contains("ultrawide") ? body.classList.remove("ultrawide"):body.classList.add("ultrawide");
}
function toggleTimeInput(toggleInput) {
if(toggleInput == "UTC") {
document.getElementById("BusTimeStart").style.display = "none";
document.getElementById("BusTimeEnd").style.display = "none";
document.getElementById("StreamStart").style.display = "";
document.getElementById("StreamEnd").style.display = "";
} else {
document.getElementById("StreamStart").style.display = "none";
document.getElementById("StreamEnd").style.display = "none";
document.getElementById("BusTimeStart").style.display = "";
document.getElementById("BusTimeEnd").style.display = "";
}
}
</script>
<div class="g-signin2" data-onsuccess="onSignIn"></div>
<a href="#" onclick="signOut();">Sign out</a>
<script>
var user;
function onSignIn(googleUser) {
user = googleUser;
var profile = googleUser.getBasicProfile();
console.log('ID: ' + profile.getId()); // Do not send to your backend! Use an ID token instead.
console.log('Name: ' + profile.getName());
console.log('Image URL: ' + profile.getImageUrl());
console.log('Email: ' + profile.getEmail()); // This is null if the 'email' scope is not present.
console.log('ID Token: ' + googleUser.getAuthResponse().id_token);
}
function signOut() {
user = null;
var auth2 = gapi.auth2.getAuthInstance();
auth2.signOut().then(function () {
console.log('User signed out.');
});
}
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,2 @@
/*! @name videojs-contrib-quality-levels @version 2.0.9 @license Apache-2.0 */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("video.js"),require("global/document")):"function"==typeof define&&define.amd?define(["video.js","global/document"],t):e.videojsContribQualityLevels=t(e.videojs,e.document)}(this,function(e,t){"use strict";function n(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}e=e&&e.hasOwnProperty("default")?e.default:e,t=t&&t.hasOwnProperty("default")?t.default:t;var r=function(r){var i,l;function o(){var i,l=n(n(i=r.call(this)||this));if(e.browser.IS_IE8)for(var s in l=t.createElement("custom"),o.prototype)"constructor"!==s&&(l[s]=o.prototype[s]);return l.levels_=[],l.selectedIndex_=-1,Object.defineProperty(l,"selectedIndex",{get:function(){return l.selectedIndex_}}),Object.defineProperty(l,"length",{get:function(){return l.levels_.length}}),l||n(i)}l=r,(i=o).prototype=Object.create(l.prototype),i.prototype.constructor=i,i.__proto__=l;var s=o.prototype;return s.addQualityLevel=function(n){var r=this.getQualityLevelById(n.id);if(r)return r;var i=this.levels_.length;return r=new function n(r){var i=this;if(e.browser.IS_IE8)for(var l in i=t.createElement("custom"),n.prototype)"constructor"!==l&&(i[l]=n.prototype[l]);return i.id=r.id,i.label=i.id,i.width=r.width,i.height=r.height,i.bitrate=r.bandwidth,i.enabled_=r.enabled,Object.defineProperty(i,"enabled",{get:function(){return i.enabled_()},set:function(e){i.enabled_(e)}}),i}(n),""+i in this||Object.defineProperty(this,i,{get:function(){return this.levels_[i]}}),this.levels_.push(r),this.trigger({qualityLevel:r,type:"addqualitylevel"}),r},s.removeQualityLevel=function(e){for(var t=null,n=0,r=this.length;n<r;n++)if(this[n]===e){t=this.levels_.splice(n,1)[0],this.selectedIndex_===n?this.selectedIndex_=-1:this.selectedIndex_>n&&this.selectedIndex_--;break}return t&&this.trigger({qualityLevel:e,type:"removequalitylevel"}),t},s.getQualityLevelById=function(e){for(var t=0,n=this.length;t<n;t++){var r=this[t];if(r.id===e)return r}return null},s.dispose=function(){this.selectedIndex_=-1,this.levels_.length=0},o}(e.EventTarget);for(var i in r.prototype.allowedEvents_={change:"change",addqualitylevel:"addqualitylevel",removequalitylevel:"removequalitylevel"},r.prototype.allowedEvents_)r.prototype["on"+i]=null;var l=function(t){return n=this,e.mergeOptions({},t),i=n.qualityLevels,l=new r,n.on("dispose",function e(){l.dispose(),n.qualityLevels=i,n.off("dispose",e)}),n.qualityLevels=function(){return l},n.qualityLevels.VERSION="2.0.9",l;var n,i,l};return(e.registerPlugin||e.plugin)("qualityLevels",l),l.VERSION="2.0.9",l});

@ -0,0 +1,7 @@
/**
* videojs-hls-quality-selector
* @version 1.0.4
* @copyright 2019 Chris Boustead (chris@forgemotion.com)
* @license MIT
*/
.video-js.vjs-hls-quality-selector{display:block}

File diff suppressed because one or more lines are too long

@ -0,0 +1,456 @@
/*! @name videojs-trimming-controls @version 0.0.0 @license MIT */
'use strict';
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var videojs = _interopDefault(require('video.js'));
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
var version = "0.0.0";
var Plugin = videojs.getPlugin('plugin'); // Default options for the plugin.
var defaults = {
startTrim: 60,
endTrim: 120,
limitPlayback: false
};
/**
* An advanced Video.js plugin. For more information on the API
*
* See: https://blog.videojs.com/feature-spotlight-advanced-plugins/
*/
var TrimmingControls =
/*#__PURE__*/
function (_Plugin) {
_inheritsLoose(TrimmingControls, _Plugin);
/**
* Create a TrimmingControls 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.
*/
function TrimmingControls(player, options) {
var _this;
// the parent class will add player under this.player
_this = _Plugin.call(this, player) || this;
_this.options = videojs.mergeOptions(defaults, options);
_this.createTrimmingControls();
player.ready(function () {
setTimeout(function () {
_this.updateTrimTimes(_this.options.startTrim, _this.options.endTrim);
}, 100);
player.on("timeupdate", function () {
if (_this.options.limitPlayback && _this.player.currentTime() >= _this.options.endTrim) {
_this.player.currentTime(_this.options.endTrim);
_this.player.pause();
}
});
player.on('playing', function () {
videojs.log('playback began!');
_this.updateTrimTimes(_this.options.startTrim, _this.options.endTrim);
});
});
return _this;
}
var _proto = TrimmingControls.prototype;
_proto.createTrimmingControls = function createTrimmingControls() {
var player = this.player;
var videoJsComponentClass = videojs.getComponent('Component');
/**
* Extend vjs button class for quality button.
*/
var TrimControlBarClass =
/*#__PURE__*/
function (_videoJsComponentClas) {
_inheritsLoose(TrimControlBarClass, _videoJsComponentClas);
/**
* Component constructor.
*/
function TrimControlBarClass() {
return _videoJsComponentClas.call(this, player, {
title: player.localize('Trimming Controls')
}) || this;
}
var _proto2 = TrimControlBarClass.prototype;
_proto2.createEl = function createEl() {
return videojs.dom.createEl('div', {
// Prefixing classes of elements within a player with "vjs-" is a convention used in Video.js.
className: 'vjs-control-bar vjs-trimming-controls',
dir: 'ltr'
});
};
return TrimControlBarClass;
}(videoJsComponentClass);
var videoJSSpacerClass = videojs.getComponent('Spacer');
var videoJSButtonClass = videojs.getComponent('Button');
var GoToButtonClass =
/*#__PURE__*/
function (_videoJSButtonClass) {
_inheritsLoose(GoToButtonClass, _videoJSButtonClass);
function GoToButtonClass(_plugin, _targetPosition, _text) {
var _this2;
_this2 = _videoJSButtonClass.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this2.trimmingControls = _plugin;
_this2.targetPosition = _targetPosition;
_this2.controlText(_text);
_this2.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this2;
}
var _proto3 = GoToButtonClass.prototype;
_proto3.handleClick = function handleClick() {
if (this.targetPosition == 0) {
this.player().currentTime(this.trimmingControls.options.startTrim);
} else if (this.targetPosition == 1) {
this.player().currentTime(this.trimmingControls.options.endTrim);
}
};
return GoToButtonClass;
}(videoJSButtonClass);
var TrimButtonClass =
/*#__PURE__*/
function (_videoJSButtonClass2) {
_inheritsLoose(TrimButtonClass, _videoJSButtonClass2);
function TrimButtonClass(_plugin, _targetPosition, _text) {
var _this3;
_this3 = _videoJSButtonClass2.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this3.trimmingControls = _plugin;
_this3.targetPosition = _targetPosition;
_this3.controlText(_text);
_this3.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this3;
}
var _proto4 = TrimButtonClass.prototype;
_proto4.handleClick = function handleClick() {
if (this.targetPosition == 0) {
this.trimmingControls.updateTrimTimes(this.player().currentTime(), this.trimmingControls.options.endTrim);
} else if (this.targetPosition == 1) {
this.trimmingControls.updateTrimTimes(this.trimmingControls.options.startTrim, this.player().currentTime());
}
};
return TrimButtonClass;
}(videoJSButtonClass);
var TrimTimeDisplayClass =
/*#__PURE__*/
function (_videoJsComponentClas2) {
_inheritsLoose(TrimTimeDisplayClass, _videoJsComponentClas2);
function TrimTimeDisplayClass(_defaultTime) {
var _this4;
_this4 = _videoJsComponentClas2.call(this, player, {}) || this;
_this4.updateTimeContent(_defaultTime);
return _this4;
}
var _proto5 = TrimTimeDisplayClass.prototype;
_proto5.createEl = function createEl() {
return videojs.dom.createEl('input', {
// Prefixing classes of elements within a player with "vjs-" is a convention used in Video.js.
className: 'vjs-time-display'
});
};
_proto5.updateTimeContent = function updateTimeContent(timeInSeconds) {
videojs.dom.emptyEl(this.el()); //this.controlText(videojs.formatTime(timeInSeconds, 600))
//videojs.dom.appendContent(this.el(), videojs.formatTime(timeInSeconds, 600));
//videojs.dom.textContent(this.el(), videojs.formatTime(timeInSeconds, 600));
this.el().value = videojs.formatTime(timeInSeconds, 600.01);
};
return TrimTimeDisplayClass;
}(videoJsComponentClass);
var FrameButtonClass =
/*#__PURE__*/
function (_videoJSButtonClass3) {
_inheritsLoose(FrameButtonClass, _videoJSButtonClass3);
function FrameButtonClass(_plugin, _targetPosition, _text) {
var _this5;
_this5 = _videoJSButtonClass3.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this5.trimmingControls = _plugin;
_this5.targetPosition = _targetPosition;
_this5.controlText(_text);
_this5.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this5;
}
var _proto6 = FrameButtonClass.prototype;
_proto6.handleClick = function handleClick() {
if (this.targetPosition == 0) {
this.player().currentTime(this.player().currentTime() - 0.1);
} else if (this.targetPosition == 1) {
this.player().currentTime(this.player().currentTime() + 0.1);
}
};
return FrameButtonClass;
}(videoJSButtonClass);
var videoJSPlayToggleClass = videojs.getComponent('PlayToggle');
var playbackEndToggleClass =
/*#__PURE__*/
function (_videoJSButtonClass4) {
_inheritsLoose(playbackEndToggleClass, _videoJSButtonClass4);
function playbackEndToggleClass(_plugin, _text) {
var _this6;
_this6 = _videoJSButtonClass4.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this6.trimmingControls = _plugin;
_this6.controlText(_text);
_this6.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this6;
}
var _proto7 = playbackEndToggleClass.prototype;
_proto7.handleClick = function handleClick() {
this.trimmingControls.options.limitPlayback = !this.trimmingControls.options.limitPlayback;
this.toggleClass('playbackLimited');
};
return playbackEndToggleClass;
}(videoJSButtonClass); //Creating Trimming Seek Bar
this._trimSeekControlBar = new TrimControlBarClass();
var trimSeekControlBarInstance = player.addChild(this._trimSeekControlBar, {
componentClass: 'trimControlBar'
}, player.children().length);
trimSeekControlBarInstance.addClass('vljs-trim-seek');
trimSeekControlBarInstance.el().innerHTML = '<div id="trimBarPlaceholderContainer"><div id="trimBarPlaceholder"></div></div>'; // //Spacer
// this._spacer1 = new videoJSSpacerClass();
// const spacer1Instance = this._trimSeekControlBar.addChild(this._spacer1, {componentClass: 'spacer'}, 0);
// spacer1Instance.setAttribute("style", "flex: 0 0 158px");
// //Spacer
// this._spacer1 = new videoJSSpacerClass();
// const spacer2Instance = this._trimSeekControlBar.addChild(this._spacer1, {componentClass: 'spacer'}, 2);
// spacer2Instance.setAttribute("style", "flex: 0 0 178px");
//Creating Trimming Controls Bar
this._trimControlBar = new TrimControlBarClass();
var trimControlBarInstance = player.addChild(this._trimControlBar, {
componentClass: 'trimControlBar'
}, player.children().length);
trimControlBarInstance.addClass('vljs-trim-buttons'); //Trim Bar Controls order: spacer,GoTo,Time,SetPlayhead,frameadjust,playpause,frameAdjust,setPlayhead,time,GoTo, endplayback
//Spacer for balance
this._spacer = new videoJSSpacerClass();
var spacerInstance = this._trimControlBar.addChild(this._spacer, {
componentClass: 'spacer'
}, 0); //Go To start of Trim
this._startGoToButton = new GoToButtonClass(this, 0, "Go to start of trim segment");
var startGoToButtonInstance = this._trimControlBar.addChild(this._startGoToButton, {
componentClass: 'goToButton'
}, 5);
startGoToButtonInstance.addClass('vljs-trimming-button');
startGoToButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "skip_previous"; //Create trim start time display
this._startTrimTimeDisplay = new TrimTimeDisplayClass(this.options.startTrim);
var startTrimTimeDisplayInstance = this._trimControlBar.addChild(this._startTrimTimeDisplay, {
componentClass: 'trimTimeDisplay'
}, 10);
startTrimTimeDisplayInstance.on("change", function () {
this.player_.trimmingControls().setTimestamps(startTrimTimeDisplayInstance.el().value, 0);
}); //Create set start at playhead button
this._startTrimButton = new TrimButtonClass(this, 0, "Set trim start at playhead");
var startTrimButtonInstance = this._trimControlBar.addChild(this._startTrimButton, {
componentClass: 'trimButton'
}, 20);
startTrimButtonInstance.addClass('vljs-trimming-button');
startTrimButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "edit"; //Create Frame Back Button
this._frameBackButton = new FrameButtonClass(this, 0, "Move back 1 frame");
var frameBackButtonInstance = this._trimControlBar.addChild(this._frameBackButton, {
componentClass: 'frameButton'
}, 22);
frameBackButtonInstance.addClass('vljs-trimming-button');
frameBackButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "fast_rewind"; //Create Play/Pause Button
this._playPauseButton = new videoJSPlayToggleClass(this.player);
var playPauseButtonInstance = this._trimControlBar.addChild(this._playPauseButton, {
componentClass: 'playPauseButton'
}, 25); //Create Frame Forward Button
this._frameForwardButton = new FrameButtonClass(this, 1, "Move forward 1 frame");
var frameForwardButtonInstance = this._trimControlBar.addChild(this._frameForwardButton, {
componentClass: 'frameButton'
}, 27);
frameForwardButtonInstance.addClass('vljs-trimming-button');
frameForwardButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "fast_forward"; //Create set end at playhead button
this._endTrimButton = new TrimButtonClass(this, 1, "Set trim end at playhead");
var endTrimButtonInstance = this._trimControlBar.addChild(this._endTrimButton, {
componentClass: 'trimButton'
}, 30);
endTrimButtonInstance.addClass('vljs-trimming-button');
endTrimButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "edit"; //Create trim end time display
this._endTrimTimeDisplay = new TrimTimeDisplayClass(this.options.endTrim);
var endTrimTimeDisplayInstance = this._trimControlBar.addChild(this._endTrimTimeDisplay, {
componentClass: 'trimTimeDisplay'
}, 40);
endTrimTimeDisplayInstance.on("change", function () {
this.player_.trimmingControls().setTimestamps(endTrimTimeDisplayInstance.el().value, 1);
}); //Go To end of Trim
this._endGoToButton = new GoToButtonClass(this, 1, "Go to end of trim segment");
var endGoToButtonInstance = this._trimControlBar.addChild(this._endGoToButton, {
componentClass: 'goToButton'
}, 50);
endGoToButtonInstance.addClass('vljs-trimming-button');
endGoToButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "skip_next"; //End playback at trim endpoint
this._playbackEndToggle = new playbackEndToggleClass(this, "End playback at trim endpoint");
var playbackEndToggleInstance = this._trimControlBar.addChild(this._playbackEndToggle, {
componentClass: 'playbackEndToggle'
}, 60);
playbackEndToggleInstance.addClass('vljs-trimming-button');
playbackEndToggleInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "stop";
};
_proto.updateTrimTimes = function updateTrimTimes(startValue, endValue) {
//Update stored values
this.options.startTrim = startValue;
this.options.endTrim = endValue; //Update timestamp displays
this._startTrimTimeDisplay.updateTimeContent(startValue);
this._endTrimTimeDisplay.updateTimeContent(endValue); //Update slider
document.getElementById("trimBarPlaceholder").style["marginLeft"] = startValue / this.player.duration() * 100 + "%";
document.getElementById("trimBarPlaceholder").style["marginRight"] = 100 - endValue / this.player.duration() * 100 + "%";
};
_proto.setTimestamps = function setTimestamps(value, index) {
if (/^\d*:?\d*:?\d*\.?\d*$/.test(value)) {
if (index === 0) {
this.updateTrimTimes(this.getSeconds(value), this.options.endTrim);
} else if (index === 1) {
this.updateTrimTimes(this.options.startTrim, this.getSeconds(value));
}
} else {
this._startTrimTimeDisplay.updateTimeContent(startValue);
this._endTrimTimeDisplay.updateTimeContent(endValue);
}
};
_proto.getSeconds = function getSeconds(time) {
var timeArr = time.split(':'),
//Array of hours, minutes, and seconds.
s = 0,
//Seconds total
m = 1; //Multiplier
while (timeArr.length > 0) {
//Iterate through time segments starting from the seconds,
s += m * timeArr.pop(); //multiply as appropriate, and add to seconds total,
m *= 60; //increase multiplier.
}
return s;
};
return TrimmingControls;
}(Plugin); // Define default values for the plugin's `state` object here.
TrimmingControls.defaultState = {}; // Include the version number.
TrimmingControls.VERSION = version; // Register the plugin with video.js.
videojs.registerPlugin('trimmingControls', TrimmingControls);
module.exports = TrimmingControls;

@ -0,0 +1 @@
.video-js .vjs-control-bar{bottom:-3em;padding-top:3px}.video-js .vjs-progress-control.vjs-control{position:absolute;width:100%;margin-top:-16px}.video-js .vjs-current-time.vjs-time-control.vjs-control{flex:auto;text-align:right}.video-js .vjs-duration.vjs-time-control.vjs-control{flex:auto;text-align:left}.video-js .vjs-control-bar.vjs-trimming-controls.vljs-trim-seek{bottom:-6em;padding-top:0px}.video-js #trimBarPlaceholderContainer{background-color:rgba(115,133,159,0.5);margin-top:5px;margin-left:10px;margin-right:10px;height:3px;width:100%}.video-js #trimBarPlaceholder{background-color:darkorange;height:3px}.video-js .vjs-control-bar.vjs-trimming-controls.vljs-trim-buttons{bottom:-9em;padding-top:0px;justify-content:space-evenly}.video-js .vjs-time-display{width:50px;height:27px;text-align:center}.video-js .vljs-trimming-button .vjs-icon-placeholder{cursor:pointer}.video-js .playbackLimited{color:darkred}

@ -0,0 +1,452 @@
/*! @name videojs-trimming-controls @version 0.0.0 @license MIT */
import videojs from 'video.js';
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
var version = "0.0.0";
var Plugin = videojs.getPlugin('plugin'); // Default options for the plugin.
var defaults = {
startTrim: 60,
endTrim: 120,
limitPlayback: false
};
/**
* An advanced Video.js plugin. For more information on the API
*
* See: https://blog.videojs.com/feature-spotlight-advanced-plugins/
*/
var TrimmingControls =
/*#__PURE__*/
function (_Plugin) {
_inheritsLoose(TrimmingControls, _Plugin);
/**
* Create a TrimmingControls 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.
*/
function TrimmingControls(player, options) {
var _this;
// the parent class will add player under this.player
_this = _Plugin.call(this, player) || this;
_this.options = videojs.mergeOptions(defaults, options);
_this.createTrimmingControls();
player.ready(function () {
setTimeout(function () {
_this.updateTrimTimes(_this.options.startTrim, _this.options.endTrim);
}, 100);
player.on("timeupdate", function () {
if (_this.options.limitPlayback && _this.player.currentTime() >= _this.options.endTrim) {
_this.player.currentTime(_this.options.endTrim);
_this.player.pause();
}
});
player.on('playing', function () {
videojs.log('playback began!');
_this.updateTrimTimes(_this.options.startTrim, _this.options.endTrim);
});
});
return _this;
}
var _proto = TrimmingControls.prototype;
_proto.createTrimmingControls = function createTrimmingControls() {
var player = this.player;
var videoJsComponentClass = videojs.getComponent('Component');
/**
* Extend vjs button class for quality button.
*/
var TrimControlBarClass =
/*#__PURE__*/
function (_videoJsComponentClas) {
_inheritsLoose(TrimControlBarClass, _videoJsComponentClas);
/**
* Component constructor.
*/
function TrimControlBarClass() {
return _videoJsComponentClas.call(this, player, {
title: player.localize('Trimming Controls')
}) || this;
}
var _proto2 = TrimControlBarClass.prototype;
_proto2.createEl = function createEl() {
return videojs.dom.createEl('div', {
// Prefixing classes of elements within a player with "vjs-" is a convention used in Video.js.
className: 'vjs-control-bar vjs-trimming-controls',
dir: 'ltr'
});
};
return TrimControlBarClass;
}(videoJsComponentClass);
var videoJSSpacerClass = videojs.getComponent('Spacer');
var videoJSButtonClass = videojs.getComponent('Button');
var GoToButtonClass =
/*#__PURE__*/
function (_videoJSButtonClass) {
_inheritsLoose(GoToButtonClass, _videoJSButtonClass);
function GoToButtonClass(_plugin, _targetPosition, _text) {
var _this2;
_this2 = _videoJSButtonClass.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this2.trimmingControls = _plugin;
_this2.targetPosition = _targetPosition;
_this2.controlText(_text);
_this2.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this2;
}
var _proto3 = GoToButtonClass.prototype;
_proto3.handleClick = function handleClick() {
if (this.targetPosition == 0) {
this.player().currentTime(this.trimmingControls.options.startTrim);
} else if (this.targetPosition == 1) {
this.player().currentTime(this.trimmingControls.options.endTrim);
}
};
return GoToButtonClass;
}(videoJSButtonClass);
var TrimButtonClass =
/*#__PURE__*/
function (_videoJSButtonClass2) {
_inheritsLoose(TrimButtonClass, _videoJSButtonClass2);
function TrimButtonClass(_plugin, _targetPosition, _text) {
var _this3;
_this3 = _videoJSButtonClass2.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this3.trimmingControls = _plugin;
_this3.targetPosition = _targetPosition;
_this3.controlText(_text);
_this3.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this3;
}
var _proto4 = TrimButtonClass.prototype;
_proto4.handleClick = function handleClick() {
if (this.targetPosition == 0) {
this.trimmingControls.updateTrimTimes(this.player().currentTime(), this.trimmingControls.options.endTrim);
} else if (this.targetPosition == 1) {
this.trimmingControls.updateTrimTimes(this.trimmingControls.options.startTrim, this.player().currentTime());
}
};
return TrimButtonClass;
}(videoJSButtonClass);
var TrimTimeDisplayClass =
/*#__PURE__*/
function (_videoJsComponentClas2) {
_inheritsLoose(TrimTimeDisplayClass, _videoJsComponentClas2);
function TrimTimeDisplayClass(_defaultTime) {
var _this4;
_this4 = _videoJsComponentClas2.call(this, player, {}) || this;
_this4.updateTimeContent(_defaultTime);
return _this4;
}
var _proto5 = TrimTimeDisplayClass.prototype;
_proto5.createEl = function createEl() {
return videojs.dom.createEl('input', {
// Prefixing classes of elements within a player with "vjs-" is a convention used in Video.js.
className: 'vjs-time-display'
});
};
_proto5.updateTimeContent = function updateTimeContent(timeInSeconds) {
videojs.dom.emptyEl(this.el()); //this.controlText(videojs.formatTime(timeInSeconds, 600))
//videojs.dom.appendContent(this.el(), videojs.formatTime(timeInSeconds, 600));
//videojs.dom.textContent(this.el(), videojs.formatTime(timeInSeconds, 600));
this.el().value = videojs.formatTime(timeInSeconds, 600.01);
};
return TrimTimeDisplayClass;
}(videoJsComponentClass);
var FrameButtonClass =
/*#__PURE__*/
function (_videoJSButtonClass3) {
_inheritsLoose(FrameButtonClass, _videoJSButtonClass3);
function FrameButtonClass(_plugin, _targetPosition, _text) {
var _this5;
_this5 = _videoJSButtonClass3.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this5.trimmingControls = _plugin;
_this5.targetPosition = _targetPosition;
_this5.controlText(_text);
_this5.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this5;
}
var _proto6 = FrameButtonClass.prototype;
_proto6.handleClick = function handleClick() {
if (this.targetPosition == 0) {
this.player().currentTime(this.player().currentTime() - 0.1);
} else if (this.targetPosition == 1) {
this.player().currentTime(this.player().currentTime() + 0.1);
}
};
return FrameButtonClass;
}(videoJSButtonClass);
var videoJSPlayToggleClass = videojs.getComponent('PlayToggle');
var playbackEndToggleClass =
/*#__PURE__*/
function (_videoJSButtonClass4) {
_inheritsLoose(playbackEndToggleClass, _videoJSButtonClass4);
function playbackEndToggleClass(_plugin, _text) {
var _this6;
_this6 = _videoJSButtonClass4.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this6.trimmingControls = _plugin;
_this6.controlText(_text);
_this6.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this6;
}
var _proto7 = playbackEndToggleClass.prototype;
_proto7.handleClick = function handleClick() {
this.trimmingControls.options.limitPlayback = !this.trimmingControls.options.limitPlayback;
this.toggleClass('playbackLimited');
};
return playbackEndToggleClass;
}(videoJSButtonClass); //Creating Trimming Seek Bar
this._trimSeekControlBar = new TrimControlBarClass();
var trimSeekControlBarInstance = player.addChild(this._trimSeekControlBar, {
componentClass: 'trimControlBar'
}, player.children().length);
trimSeekControlBarInstance.addClass('vljs-trim-seek');
trimSeekControlBarInstance.el().innerHTML = '<div id="trimBarPlaceholderContainer"><div id="trimBarPlaceholder"></div></div>'; // //Spacer
// this._spacer1 = new videoJSSpacerClass();
// const spacer1Instance = this._trimSeekControlBar.addChild(this._spacer1, {componentClass: 'spacer'}, 0);
// spacer1Instance.setAttribute("style", "flex: 0 0 158px");
// //Spacer
// this._spacer1 = new videoJSSpacerClass();
// const spacer2Instance = this._trimSeekControlBar.addChild(this._spacer1, {componentClass: 'spacer'}, 2);
// spacer2Instance.setAttribute("style", "flex: 0 0 178px");
//Creating Trimming Controls Bar
this._trimControlBar = new TrimControlBarClass();
var trimControlBarInstance = player.addChild(this._trimControlBar, {
componentClass: 'trimControlBar'
}, player.children().length);
trimControlBarInstance.addClass('vljs-trim-buttons'); //Trim Bar Controls order: spacer,GoTo,Time,SetPlayhead,frameadjust,playpause,frameAdjust,setPlayhead,time,GoTo, endplayback
//Spacer for balance
this._spacer = new videoJSSpacerClass();
var spacerInstance = this._trimControlBar.addChild(this._spacer, {
componentClass: 'spacer'
}, 0); //Go To start of Trim
this._startGoToButton = new GoToButtonClass(this, 0, "Go to start of trim segment");
var startGoToButtonInstance = this._trimControlBar.addChild(this._startGoToButton, {
componentClass: 'goToButton'
}, 5);
startGoToButtonInstance.addClass('vljs-trimming-button');
startGoToButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "skip_previous"; //Create trim start time display
this._startTrimTimeDisplay = new TrimTimeDisplayClass(this.options.startTrim);
var startTrimTimeDisplayInstance = this._trimControlBar.addChild(this._startTrimTimeDisplay, {
componentClass: 'trimTimeDisplay'
}, 10);
startTrimTimeDisplayInstance.on("change", function () {
this.player_.trimmingControls().setTimestamps(startTrimTimeDisplayInstance.el().value, 0);
}); //Create set start at playhead button
this._startTrimButton = new TrimButtonClass(this, 0, "Set trim start at playhead");
var startTrimButtonInstance = this._trimControlBar.addChild(this._startTrimButton, {
componentClass: 'trimButton'
}, 20);
startTrimButtonInstance.addClass('vljs-trimming-button');
startTrimButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "edit"; //Create Frame Back Button
this._frameBackButton = new FrameButtonClass(this, 0, "Move back 1 frame");
var frameBackButtonInstance = this._trimControlBar.addChild(this._frameBackButton, {
componentClass: 'frameButton'
}, 22);
frameBackButtonInstance.addClass('vljs-trimming-button');
frameBackButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "fast_rewind"; //Create Play/Pause Button
this._playPauseButton = new videoJSPlayToggleClass(this.player);
var playPauseButtonInstance = this._trimControlBar.addChild(this._playPauseButton, {
componentClass: 'playPauseButton'
}, 25); //Create Frame Forward Button
this._frameForwardButton = new FrameButtonClass(this, 1, "Move forward 1 frame");
var frameForwardButtonInstance = this._trimControlBar.addChild(this._frameForwardButton, {
componentClass: 'frameButton'
}, 27);
frameForwardButtonInstance.addClass('vljs-trimming-button');
frameForwardButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "fast_forward"; //Create set end at playhead button
this._endTrimButton = new TrimButtonClass(this, 1, "Set trim end at playhead");
var endTrimButtonInstance = this._trimControlBar.addChild(this._endTrimButton, {
componentClass: 'trimButton'
}, 30);
endTrimButtonInstance.addClass('vljs-trimming-button');
endTrimButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "edit"; //Create trim end time display
this._endTrimTimeDisplay = new TrimTimeDisplayClass(this.options.endTrim);
var endTrimTimeDisplayInstance = this._trimControlBar.addChild(this._endTrimTimeDisplay, {
componentClass: 'trimTimeDisplay'
}, 40);
endTrimTimeDisplayInstance.on("change", function () {
this.player_.trimmingControls().setTimestamps(endTrimTimeDisplayInstance.el().value, 1);
}); //Go To end of Trim
this._endGoToButton = new GoToButtonClass(this, 1, "Go to end of trim segment");
var endGoToButtonInstance = this._trimControlBar.addChild(this._endGoToButton, {
componentClass: 'goToButton'
}, 50);
endGoToButtonInstance.addClass('vljs-trimming-button');
endGoToButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "skip_next"; //End playback at trim endpoint
this._playbackEndToggle = new playbackEndToggleClass(this, "End playback at trim endpoint");
var playbackEndToggleInstance = this._trimControlBar.addChild(this._playbackEndToggle, {
componentClass: 'playbackEndToggle'
}, 60);
playbackEndToggleInstance.addClass('vljs-trimming-button');
playbackEndToggleInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "stop";
};
_proto.updateTrimTimes = function updateTrimTimes(startValue, endValue) {
//Update stored values
this.options.startTrim = startValue;
this.options.endTrim = endValue; //Update timestamp displays
this._startTrimTimeDisplay.updateTimeContent(startValue);
this._endTrimTimeDisplay.updateTimeContent(endValue); //Update slider
document.getElementById("trimBarPlaceholder").style["marginLeft"] = startValue / this.player.duration() * 100 + "%";
document.getElementById("trimBarPlaceholder").style["marginRight"] = 100 - endValue / this.player.duration() * 100 + "%";
};
_proto.setTimestamps = function setTimestamps(value, index) {
if (/^\d*:?\d*:?\d*\.?\d*$/.test(value)) {
if (index === 0) {
this.updateTrimTimes(this.getSeconds(value), this.options.endTrim);
} else if (index === 1) {
this.updateTrimTimes(this.options.startTrim, this.getSeconds(value));
}
} else {
this._startTrimTimeDisplay.updateTimeContent(startValue);
this._endTrimTimeDisplay.updateTimeContent(endValue);
}
};
_proto.getSeconds = function getSeconds(time) {
var timeArr = time.split(':'),
//Array of hours, minutes, and seconds.
s = 0,
//Seconds total
m = 1; //Multiplier
while (timeArr.length > 0) {
//Iterate through time segments starting from the seconds,
s += m * timeArr.pop(); //multiply as appropriate, and add to seconds total,
m *= 60; //increase multiplier.
}
return s;
};
return TrimmingControls;
}(Plugin); // Define default values for the plugin's `state` object here.
TrimmingControls.defaultState = {}; // Include the version number.
TrimmingControls.VERSION = version; // Register the plugin with video.js.
videojs.registerPlugin('trimmingControls', TrimmingControls);
export default TrimmingControls;

@ -0,0 +1,460 @@
/*! @name videojs-trimming-controls @version 0.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.videojsTrimmingControls = factory(global.videojs));
}(this, (function (videojs) { 'use strict';
videojs = videojs && videojs.hasOwnProperty('default') ? videojs['default'] : videojs;
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
var version = "0.0.0";
var Plugin = videojs.getPlugin('plugin'); // Default options for the plugin.
var defaults = {
startTrim: 60,
endTrim: 120,
limitPlayback: false
};
/**
* An advanced Video.js plugin. For more information on the API
*
* See: https://blog.videojs.com/feature-spotlight-advanced-plugins/
*/
var TrimmingControls =
/*#__PURE__*/
function (_Plugin) {
_inheritsLoose(TrimmingControls, _Plugin);
/**
* Create a TrimmingControls 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.
*/
function TrimmingControls(player, options) {
var _this;
// the parent class will add player under this.player
_this = _Plugin.call(this, player) || this;
_this.options = videojs.mergeOptions(defaults, options);
_this.createTrimmingControls();
player.ready(function () {
setTimeout(function () {
_this.updateTrimTimes(_this.options.startTrim, _this.options.endTrim);
}, 100);
player.on("timeupdate", function () {
if (_this.options.limitPlayback && _this.player.currentTime() >= _this.options.endTrim) {
_this.player.currentTime(_this.options.endTrim);
_this.player.pause();
}
});
player.on('playing', function () {
videojs.log('playback began!');
_this.updateTrimTimes(_this.options.startTrim, _this.options.endTrim);
});
});
return _this;
}
var _proto = TrimmingControls.prototype;
_proto.createTrimmingControls = function createTrimmingControls() {
var player = this.player;
var videoJsComponentClass = videojs.getComponent('Component');
/**
* Extend vjs button class for quality button.
*/
var TrimControlBarClass =
/*#__PURE__*/
function (_videoJsComponentClas) {
_inheritsLoose(TrimControlBarClass, _videoJsComponentClas);
/**
* Component constructor.
*/
function TrimControlBarClass() {
return _videoJsComponentClas.call(this, player, {
title: player.localize('Trimming Controls')
}) || this;
}
var _proto2 = TrimControlBarClass.prototype;
_proto2.createEl = function createEl() {
return videojs.dom.createEl('div', {
// Prefixing classes of elements within a player with "vjs-" is a convention used in Video.js.
className: 'vjs-control-bar vjs-trimming-controls',
dir: 'ltr'
});
};
return TrimControlBarClass;
}(videoJsComponentClass);
var videoJSSpacerClass = videojs.getComponent('Spacer');
var videoJSButtonClass = videojs.getComponent('Button');
var GoToButtonClass =
/*#__PURE__*/
function (_videoJSButtonClass) {
_inheritsLoose(GoToButtonClass, _videoJSButtonClass);
function GoToButtonClass(_plugin, _targetPosition, _text) {
var _this2;
_this2 = _videoJSButtonClass.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this2.trimmingControls = _plugin;
_this2.targetPosition = _targetPosition;
_this2.controlText(_text);
_this2.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this2;
}
var _proto3 = GoToButtonClass.prototype;
_proto3.handleClick = function handleClick() {
if (this.targetPosition == 0) {
this.player().currentTime(this.trimmingControls.options.startTrim);
} else if (this.targetPosition == 1) {
this.player().currentTime(this.trimmingControls.options.endTrim);
}
};
return GoToButtonClass;
}(videoJSButtonClass);
var TrimButtonClass =
/*#__PURE__*/
function (_videoJSButtonClass2) {
_inheritsLoose(TrimButtonClass, _videoJSButtonClass2);
function TrimButtonClass(_plugin, _targetPosition, _text) {
var _this3;
_this3 = _videoJSButtonClass2.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this3.trimmingControls = _plugin;
_this3.targetPosition = _targetPosition;
_this3.controlText(_text);
_this3.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this3;
}
var _proto4 = TrimButtonClass.prototype;
_proto4.handleClick = function handleClick() {
if (this.targetPosition == 0) {
this.trimmingControls.updateTrimTimes(this.player().currentTime(), this.trimmingControls.options.endTrim);
} else if (this.targetPosition == 1) {
this.trimmingControls.updateTrimTimes(this.trimmingControls.options.startTrim, this.player().currentTime());
}
};
return TrimButtonClass;
}(videoJSButtonClass);
var TrimTimeDisplayClass =
/*#__PURE__*/
function (_videoJsComponentClas2) {
_inheritsLoose(TrimTimeDisplayClass, _videoJsComponentClas2);
function TrimTimeDisplayClass(_defaultTime) {
var _this4;
_this4 = _videoJsComponentClas2.call(this, player, {}) || this;
_this4.updateTimeContent(_defaultTime);
return _this4;
}
var _proto5 = TrimTimeDisplayClass.prototype;
_proto5.createEl = function createEl() {
return videojs.dom.createEl('input', {
// Prefixing classes of elements within a player with "vjs-" is a convention used in Video.js.
className: 'vjs-time-display'
});
};
_proto5.updateTimeContent = function updateTimeContent(timeInSeconds) {
videojs.dom.emptyEl(this.el()); //this.controlText(videojs.formatTime(timeInSeconds, 600))
//videojs.dom.appendContent(this.el(), videojs.formatTime(timeInSeconds, 600));
//videojs.dom.textContent(this.el(), videojs.formatTime(timeInSeconds, 600));
this.el().value = videojs.formatTime(timeInSeconds, 600.01);
};
return TrimTimeDisplayClass;
}(videoJsComponentClass);
var FrameButtonClass =
/*#__PURE__*/
function (_videoJSButtonClass3) {
_inheritsLoose(FrameButtonClass, _videoJSButtonClass3);
function FrameButtonClass(_plugin, _targetPosition, _text) {
var _this5;
_this5 = _videoJSButtonClass3.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this5.trimmingControls = _plugin;
_this5.targetPosition = _targetPosition;
_this5.controlText(_text);
_this5.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this5;
}
var _proto6 = FrameButtonClass.prototype;
_proto6.handleClick = function handleClick() {
if (this.targetPosition == 0) {
this.player().currentTime(this.player().currentTime() - 0.1);
} else if (this.targetPosition == 1) {
this.player().currentTime(this.player().currentTime() + 0.1);
}
};
return FrameButtonClass;
}(videoJSButtonClass);
var videoJSPlayToggleClass = videojs.getComponent('PlayToggle');
var playbackEndToggleClass =
/*#__PURE__*/
function (_videoJSButtonClass4) {
_inheritsLoose(playbackEndToggleClass, _videoJSButtonClass4);
function playbackEndToggleClass(_plugin, _text) {
var _this6;
_this6 = _videoJSButtonClass4.call(this, player, {// title: player.localize('Trim Button'),
// label: "Trim Here"
}) || this;
_this6.trimmingControls = _plugin;
_this6.controlText(_text);
_this6.el().getElementsByClassName('vjs-icon-placeholder')[0].classList += " material-icons";
return _this6;
}
var _proto7 = playbackEndToggleClass.prototype;
_proto7.handleClick = function handleClick() {
this.trimmingControls.options.limitPlayback = !this.trimmingControls.options.limitPlayback;
this.toggleClass('playbackLimited');
};
return playbackEndToggleClass;
}(videoJSButtonClass); //Creating Trimming Seek Bar
this._trimSeekControlBar = new TrimControlBarClass();
var trimSeekControlBarInstance = player.addChild(this._trimSeekControlBar, {
componentClass: 'trimControlBar'
}, player.children().length);
trimSeekControlBarInstance.addClass('vljs-trim-seek');
trimSeekControlBarInstance.el().innerHTML = '<div id="trimBarPlaceholderContainer"><div id="trimBarPlaceholder"></div></div>'; // //Spacer
// this._spacer1 = new videoJSSpacerClass();
// const spacer1Instance = this._trimSeekControlBar.addChild(this._spacer1, {componentClass: 'spacer'}, 0);
// spacer1Instance.setAttribute("style", "flex: 0 0 158px");
// //Spacer
// this._spacer1 = new videoJSSpacerClass();
// const spacer2Instance = this._trimSeekControlBar.addChild(this._spacer1, {componentClass: 'spacer'}, 2);
// spacer2Instance.setAttribute("style", "flex: 0 0 178px");
//Creating Trimming Controls Bar
this._trimControlBar = new TrimControlBarClass();
var trimControlBarInstance = player.addChild(this._trimControlBar, {
componentClass: 'trimControlBar'
}, player.children().length);
trimControlBarInstance.addClass('vljs-trim-buttons'); //Trim Bar Controls order: spacer,GoTo,Time,SetPlayhead,frameadjust,playpause,frameAdjust,setPlayhead,time,GoTo, endplayback
//Spacer for balance
this._spacer = new videoJSSpacerClass();
var spacerInstance = this._trimControlBar.addChild(this._spacer, {
componentClass: 'spacer'
}, 0); //Go To start of Trim
this._startGoToButton = new GoToButtonClass(this, 0, "Go to start of trim segment");
var startGoToButtonInstance = this._trimControlBar.addChild(this._startGoToButton, {
componentClass: 'goToButton'
}, 5);
startGoToButtonInstance.addClass('vljs-trimming-button');
startGoToButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "skip_previous"; //Create trim start time display
this._startTrimTimeDisplay = new TrimTimeDisplayClass(this.options.startTrim);
var startTrimTimeDisplayInstance = this._trimControlBar.addChild(this._startTrimTimeDisplay, {
componentClass: 'trimTimeDisplay'
}, 10);
startTrimTimeDisplayInstance.on("change", function () {
this.player_.trimmingControls().setTimestamps(startTrimTimeDisplayInstance.el().value, 0);
}); //Create set start at playhead button
this._startTrimButton = new TrimButtonClass(this, 0, "Set trim start at playhead");
var startTrimButtonInstance = this._trimControlBar.addChild(this._startTrimButton, {
componentClass: 'trimButton'
}, 20);
startTrimButtonInstance.addClass('vljs-trimming-button');
startTrimButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "edit"; //Create Frame Back Button
this._frameBackButton = new FrameButtonClass(this, 0, "Move back 1 frame");
var frameBackButtonInstance = this._trimControlBar.addChild(this._frameBackButton, {
componentClass: 'frameButton'
}, 22);
frameBackButtonInstance.addClass('vljs-trimming-button');
frameBackButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "fast_rewind"; //Create Play/Pause Button
this._playPauseButton = new videoJSPlayToggleClass(this.player);
var playPauseButtonInstance = this._trimControlBar.addChild(this._playPauseButton, {
componentClass: 'playPauseButton'
}, 25); //Create Frame Forward Button
this._frameForwardButton = new FrameButtonClass(this, 1, "Move forward 1 frame");
var frameForwardButtonInstance = this._trimControlBar.addChild(this._frameForwardButton, {
componentClass: 'frameButton'
}, 27);
frameForwardButtonInstance.addClass('vljs-trimming-button');
frameForwardButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "fast_forward"; //Create set end at playhead button
this._endTrimButton = new TrimButtonClass(this, 1, "Set trim end at playhead");
var endTrimButtonInstance = this._trimControlBar.addChild(this._endTrimButton, {
componentClass: 'trimButton'
}, 30);
endTrimButtonInstance.addClass('vljs-trimming-button');
endTrimButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "edit"; //Create trim end time display
this._endTrimTimeDisplay = new TrimTimeDisplayClass(this.options.endTrim);
var endTrimTimeDisplayInstance = this._trimControlBar.addChild(this._endTrimTimeDisplay, {
componentClass: 'trimTimeDisplay'
}, 40);
endTrimTimeDisplayInstance.on("change", function () {
this.player_.trimmingControls().setTimestamps(endTrimTimeDisplayInstance.el().value, 1);
}); //Go To end of Trim
this._endGoToButton = new GoToButtonClass(this, 1, "Go to end of trim segment");
var endGoToButtonInstance = this._trimControlBar.addChild(this._endGoToButton, {
componentClass: 'goToButton'
}, 50);
endGoToButtonInstance.addClass('vljs-trimming-button');
endGoToButtonInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "skip_next"; //End playback at trim endpoint
this._playbackEndToggle = new playbackEndToggleClass(this, "End playback at trim endpoint");
var playbackEndToggleInstance = this._trimControlBar.addChild(this._playbackEndToggle, {
componentClass: 'playbackEndToggle'
}, 60);
playbackEndToggleInstance.addClass('vljs-trimming-button');
playbackEndToggleInstance.el().getElementsByClassName('vjs-icon-placeholder')[0].innerText = "stop";
};
_proto.updateTrimTimes = function updateTrimTimes(startValue, endValue) {
//Update stored values
this.options.startTrim = startValue;
this.options.endTrim = endValue; //Update timestamp displays
this._startTrimTimeDisplay.updateTimeContent(startValue);
this._endTrimTimeDisplay.updateTimeContent(endValue); //Update slider
document.getElementById("trimBarPlaceholder").style["marginLeft"] = startValue / this.player.duration() * 100 + "%";
document.getElementById("trimBarPlaceholder").style["marginRight"] = 100 - endValue / this.player.duration() * 100 + "%";
};
_proto.setTimestamps = function setTimestamps(value, index) {
if (/^\d*:?\d*:?\d*\.?\d*$/.test(value)) {
if (index === 0) {
this.updateTrimTimes(this.getSeconds(value), this.options.endTrim);
} else if (index === 1) {
this.updateTrimTimes(this.options.startTrim, this.getSeconds(value));
}
} else {
this._startTrimTimeDisplay.updateTimeContent(startValue);
this._endTrimTimeDisplay.updateTimeContent(endValue);
}
};
_proto.getSeconds = function getSeconds(time) {
var timeArr = time.split(':'),
//Array of hours, minutes, and seconds.
s = 0,
//Seconds total
m = 1; //Multiplier
while (timeArr.length > 0) {
//Iterate through time segments starting from the seconds,
s += m * timeArr.pop(); //multiply as appropriate, and add to seconds total,
m *= 60; //increase multiplier.
}
return s;
};
return TrimmingControls;
}(Plugin); // Define default values for the plugin's `state` object here.
TrimmingControls.defaultState = {}; // Include the version number.
TrimmingControls.VERSION = version; // Register the plugin with video.js.
videojs.registerPlugin('trimmingControls', TrimmingControls);
return TrimmingControls;
})));

File diff suppressed because one or more lines are too long

@ -0,0 +1,168 @@
var desertBusStart = new Date("1970-01-01T00:00:00Z");
pageSetup = function() {
//Get values from ThrimShim
if(/id=/.test(document.location.search)) {
var rowId = /id=(.*)(?:&|$)/.exec(document.location.search)[1];
fetch("/thrimshim/"+rowId).then(data => data.json()).then(function (data) {
if (!data) {
alert("No video available for stream.");
return;
}
//data = testThrimShim;
desertBusStart = new Date(data.bustime_start);
document.getElementById("hiddenSubmissionID").value = data.id;
document.getElementById("StreamName").value = data.video_channel ? data.video_channel:document.getElementById("StreamName").value;
document.getElementById("StreamStart").value = data.event_start;
document.getElementById("BusTimeStart").value = (new Date(data.event_start+"Z") < desertBusStart ? "-":"") + videojs.formatTime(Math.abs((new Date(data.event_start+"Z") - desertBusStart)/1000), 600.01).padStart(7, "0:");
document.getElementById("StreamEnd").value = data.event_end;
document.getElementById("BusTimeEnd").value = (new Date(data.event_end+"Z") < desertBusStart ? "-":"") + videojs.formatTime(Math.abs((new Date(data.event_end+"Z") - desertBusStart)/1000), 600.01).padStart(7, "0:");
document.getElementById("VideoTitle").value = data.video_title ? data.video_title:document.getElementById("VideoTitle").value;
document.getElementById("VideoDescription").value = data.video_description ? data.video_description:data.description;
loadPlaylist(data.video_start, data.video_end);
});
}
else {
document.getElementById('SubmitButton').disabled = true;
var startOfHour = new Date(new Date().setMinutes(0,0,0));
document.getElementById("StreamStart").value = new Date(startOfHour.getTime() - 1000*60*60).toISOString().substring(0,19);
document.getElementById("StreamEnd").value = startOfHour.toISOString().substring(0,19);
loadPlaylist();
}
};
loadPlaylist = function(startTrim, endTrim) {
var playlist = "/playlist/" + document.getElementById("StreamName").value + ".m3u8";
if(document.getElementById("BusTimeToggleBus").checked) {
var streamStart = desertBusStart;
var busTimeStart = document.getElementById("BusTimeStart").value;
var busTimeEnd = document.getElementById("BusTimeEnd").value;
//Convert BusTime to milliseconds from start of stream
busTimeStart = (parseInt(busTimeStart.split(':')[0]) + busTimeStart.split(':')[1]/60) * 1000 * 60 * 60;
busTimeEnd = (parseInt(busTimeEnd.split(':')[0]) + busTimeEnd.split(':')[1]/60) * 1000 * 60 * 60;
document.getElementById("StreamStart").value = new Date(streamStart.getTime() + busTimeStart).toISOString().substring(0,19);
document.getElementById("StreamEnd").value = new Date(streamStart.getTime() + busTimeEnd).toISOString().substring(0,19);
}
var streamStart = document.getElementById("StreamStart").value ? "start="+document.getElementById("StreamStart").value:null;
var streamEnd = document.getElementById("StreamEnd").value ? "end="+document.getElementById("StreamEnd").value:null;
var queryString = (streamStart || streamEnd) ? "?" + [streamStart, streamEnd].filter((a) => !!a).join("&"):"";
setupPlayer(playlist + queryString, startTrim, endTrim);
//Get quality levels for advanced properties.
document.getElementById('qualityLevel').innerHTML = "";
fetch('/files/' + document.getElementById('StreamName').value).then(data => data.json()).then(function (data) {
if (!data.length) {
console.log("Could not retrieve quality levels");
return;
}
var qualityLevels = data.sort().reverse();
qualityLevels.forEach(function(level, index) {
document.getElementById('qualityLevel').innerHTML += '<option value="'+level+'" '+(index==0 ? 'selected':'')+'>'+level+'</option>';
});
});
};
thrimbletrimmerSubmit = function() {
document.getElementById('SubmitButton').disabled = true;
if(player.trimmingControls().options.startTrim >= player.trimmingControls().options.endTrim) {
alert("End Time must be greater than Start Time");
document.getElementById('SubmitButton').disabled = false;
} else {
var discontinuities = mapDiscontinuities();
var wubData = {
video_start:getRealTimeForPlayerTime(discontinuities, player.trimmingControls().options.startTrim).replace('Z',''),
video_end:getRealTimeForPlayerTime(discontinuities, player.trimmingControls().options.endTrim).replace('Z',''),
video_title:document.getElementById("VideoTitle").value,
video_description:document.getElementById("VideoDescription").value,
allow_holes:String(document.getElementById('AllowHoles').checked),
upload_location:document.getElementById('uploadLocation').value,
video_channel:document.getElementById("StreamName").value,
video_quality:document.getElementById('qualityLevel').options[document.getElementById('qualityLevel').options.selectedIndex].value,
uploader_whitelist:(document.getElementById('uploaderWhitelist').value ? document.getElementById('uploaderWhitelist').value.split(','):null),
state:"EDITED",
token: user.getAuthResponse().id_token
};
// state_columns = ['state', 'uploader', 'error', 'video_link']
console.log(wubData);
console.log(JSON.stringify(wubData));
//Submit to thrimshim
var rowId = /id=(.*)(?:&|$)/.exec(document.location.search)[1];
fetch("/thrimshim/"+rowId, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(wubData)
})
.then(response => { if (!response.ok) { throw Error(response.statusText); }; return response; })
.then(data => { console.log(data); setTimeout(() => { window.location.href = '/thrimbletrimmer/dashboard.html'; }, 500); })
.catch(error => { console.log(error); alert(error); });
}
};
thrimbletrimmerDownload = function() {
document.getElementById('SubmitButton').disabled = true;
if(player.trimmingControls().options.startTrim >= player.trimmingControls().options.endTrim) {
alert("End Time must be greater than Start Time");
document.getElementById('SubmitButton').disabled = false;
} else {
var discontinuities = mapDiscontinuities();
var downloadStart = getRealTimeForPlayerTime(discontinuities, player.trimmingControls().options.startTrim);
var downloadEnd = getRealTimeForPlayerTime(discontinuities, player.trimmingControls().options.endTrim);
var targetURL = "/cut/" + document.getElementById("StreamName").value +
"/"+document.getElementById('qualityLevel').options[document.getElementById('qualityLevel').options.selectedIndex].value+".ts" +
"?start=" + downloadStart +
"&end=" + downloadEnd +
"&allow_holes=" + String(document.getElementById('AllowHoles').checked);
console.log(targetURL);
document.getElementById('outputFile').src = targetURL;
}
};
thrimbletrimmerManualLink = function() {
var rowId = /id=(.*)(?:&|$)/.exec(document.location.search)[1];
fetch("/thrimshim/manual-link/"+rowId, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
link: document.getElementById("ManualLink").value,
token: user.getAuthResponse().id_token
})
})
.then(response => { if (!response.ok) { throw Error(response.statusText); }; return response; })
.then(data => { console.log(data); setTimeout(() => { alert("Manual link set"); }, 500); })
.catch(error => { console.log(error); alert(error); });
};
thrimbletrimmerResetLink = function() {
var rowId = /id=(.*)(?:&|$)/.exec(document.location.search)[1];
if(confirm('Are you sure you want to reset this event?')) {
fetch("/thrimshim/reset/"+rowId, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({token: user.getAuthResponse().id_token})
})
.then(response => { if (!response.ok) { throw Error(response.statusText); }; return response; })
.then(data => { console.log(data); setTimeout(() => { window.location.reload() }, 500); })
.catch(error => { console.log(error); alert(error); });
}
};

@ -0,0 +1,77 @@
document.addEventListener('keypress', (event) => {
//if(event.target.nodeName == "BODY") {
if(event.target.nodeName !== "INPUT" && event.target.nodeName !== "TEXTAREA") {
switch(event.key) {
case "j":
player.currentTime(player.currentTime()-10);
break;
case "k":
player.paused() ? player.play():player.pause();
break;
case "l":
player.currentTime(player.currentTime()+10);
break;
case ",":
player.currentTime(player.currentTime()-0.1);
break;
case ".":
player.currentTime(player.currentTime()+0.1);
break;
case "i":
player.trimmingControls().updateTrimTimes(player.currentTime(), player.trimmingControls().options.endTrim);
break;
case "o":
player.trimmingControls().updateTrimTimes(player.trimmingControls().options.startTrim, player.currentTime());
break;
case "0":
player.currentTime(0);
break;
case "1":
player.currentTime(player.duration()*0.1);
break;
case "2":
player.currentTime(player.duration()*0.2);
break;
case "3":
player.currentTime(player.duration()*0.3);
break;
case "4":
player.currentTime(player.duration()*0.4);
break;
case "5":
player.currentTime(player.duration()*0.5);
break;
case "6":
player.currentTime(player.duration()*0.6);
break;
case "7":
player.currentTime(player.duration()*0.7);
break;
case "8":
player.currentTime(player.duration()*0.8);
break;
case "9":
player.currentTime(player.duration()*0.9);
break;
}
}
// const keyName = event.key;
// console.log('keypress event\n\n' + 'key: ' + keyName);
// console.log(event.target.nodeName);
});
//Arrow keys only detected on keydown, keypress only works in "some" browsers
document.addEventListener('keydown', (event) => {
if(event.target.nodeName !== "INPUT" && event.target.nodeName !== "TEXTAREA") {
switch(event.keyCode) {
case 37:
player.currentTime(player.currentTime()-5);
break;
case 39:
player.currentTime(player.currentTime()+5);
break;
}
}
});

@ -0,0 +1,92 @@
var player = null;
function setupPlayer(source, startTrim, endTrim) {
document.getElementById("my-player").style.display = "";
//Make poster of DB logo in correct aspect ratio, to control initial size of fluid container.
var options = {
sources: [{ src: source }],
liveui: true,
//fluid:true,
controls:true,
autoplay:false,
width:1280,
height:420,
playbackRates: [0.5, 1, 1.25, 1.5, 2],
inactivityTimeout: 0,
controlBar: {
fullscreenToggle: false,
volumePanel: {
inline: false
}
}
};
if(player) { //Destroy and recreate the player if it already exists.
player.dispose();
document.getElementById("EditorContainer").innerHTML = `
<video id="my-player" class="video-js" controls disablePictureInPicture preload="auto">
<p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p>
</video>
`;
}
player = videojs('my-player', options, function onPlayerReady() {
videojs.log('Your player is ready!');
// In this context, `this` is the player that was created by Video.js.
this.on('ready', function() {
//this.play();
});
this.vhs.playlists.on('loadedmetadata', function() {
// setTimeout(function() { player.play(); }, 1000);
player.hasStarted(true); //So it displays all the controls.
stream_start = player.vhs.playlists.master.playlists.filter(playlist => typeof playlist.discontinuityStarts !== "undefined")[0].dateTimeObject;
startTrim = startTrim ? (new Date(startTrim+"Z")-stream_start)/1000:0;
endTrim = endTrim ? (new Date(endTrim+"Z")-stream_start)/1000:player.duration();
var trimmingControls = player.trimmingControls({ startTrim:startTrim, endTrim:endTrim });
});
// How about an event listener?
this.on('ended', function() {
videojs.log('Awww...over so soon?!');
});
this.on('error', function() {
videojs.log("Could not load video stream");
alert("No video available for stream.");
document.getElementById("my-player").style.display = "none";
})
});
var hlsQS = player.hlsQualitySelector();
//var trimmingControls = player.trimmingControls({ startTrim:(startTrim ? startTrim:0), endTrim:(endTrim ? endTrim:player.duration()) });
}
mapDiscontinuities = function() {
var playlist = player.vhs.playlists.master.playlists.filter(playlist => typeof playlist.discontinuityStarts !== "undefined")[0]; //Only one of the playlists will have the discontinuity or stream start objects, and it's not necessarily the first one or the source one.
var discontinuities = playlist.discontinuityStarts.map(segmentIndex => { return {segmentIndex:segmentIndex, segmentTimestamp:playlist.segments[segmentIndex].dateTimeObject, playbackIndex:null}; });
//var lastDiscontinuity = Math.max(...playlist.discontinuityStarts);
var lastDiscontinuity = playlist.discontinuityStarts.slice(-1).pop(); //Assumes discontinuities are sorted in ascending order.
var durationMarker = 0;
for (var index = 0; index <= lastDiscontinuity; index++) {
let segment = playlist.segments[index];
if(segment.discontinuity) {
discontinuities.find(discontinuity => discontinuity.segmentIndex == index).playbackIndex = durationMarker;
}
durationMarker += segment.duration;
}
return discontinuities;
};
getRealTimeForPlayerTime = function(discontinuities, playbackIndex) {
var streamStart = player.vhs.playlists.master.playlists.filter(playlist => typeof playlist.dateTimeObject !== "undefined")[0].dateTimeObject; //Only one of the playlists will have the discontinuity or stream start objects, and it's not necessarily the first one or the source one.
//Find last discontinuity before playbackIndex
var lastDiscontinuity = discontinuities.filter(discontinuity => discontinuity.playbackIndex < playbackIndex).slice(-1).pop();
if(lastDiscontinuity) {
streamStart = lastDiscontinuity.segmentTimestamp;
playbackIndex -= lastDiscontinuity.playbackIndex;
}
return new Date(streamStart.getTime()+playbackIndex*1000).toISOString();
};

@ -0,0 +1,37 @@
html, body {
height:100%;
margin:0px;
background-color:darkgrey;
}
body.ultrawide > div { max-width:100% !important; }
body.ultrawide .my-player-dimensions { width:100% !important; }
.video-js .vjs-control-bar {
background-color:#2b333f;
}
.video-js .vjs-time-control {
display: block;
}
.video-js .vjs-remaining-time {
display: none;
}
.video-js .vjs-current-time.vjs-time-control, .video-js .vjs-duration.vjs-time-control {
padding:0em;
}
.video-js .vjs-progress-control .vjs-mouse-display {
z-index: 100;
}
.vjs-menu-button-popup .vjs-menu {
bottom:-3px;
}
#EditorDetailsPane {
margin-top:100px;
}
#VideoTitle, #VideoDescription {
width:100%;
}
*:focus {
outline:none;
}

@ -99,7 +99,8 @@ def get_all_rows():
conn = app.db_manager.get_conn()
results = database.query(conn, """
SELECT *
FROM events""")
FROM events
ORDER BY event_start""")
rows = []
for row in results:
row = row._asdict()
@ -137,6 +138,9 @@ def get_row(ident):
else value
) for key, value in response.items()
}
if response["video_channel"] is None:
response["video_channel"] = app.default_channel
response["bustime_start"] = app.bustime_start
logging.info('Row {} fetched'.format(ident))
return json.dumps(response)
@ -267,14 +271,18 @@ def reset_row(ident, editor=None):
@argh.arg('--host', help='Address or socket server will listen to. Default is 0.0.0.0 (everything on the local machine).')
@argh.arg('--port', help='Port server will listen on. Default is 8004.')
@argh.arg('connection-string', help='Postgres connection string, which is either a space-separated list of key=value pairs, or a URI like: postgresql://USER:PASSWORD@HOST/DBNAME?KEY=VALUE')
@argh.arg('default-channel', help='The default channel this instance will serve events for')
@argh.arg('bustime-start', help='The start time in UTC for the event, for UTC-Bustime conversion')
@argh.arg('--backdoor-port', help='Port for gevent.backdoor access. By default disabled.')
@argh.arg('--no-authentication', help='Do not authenticate')
def main(connection_string, host='0.0.0.0', port=8004, backdoor_port=0,
def main(connection_string, default_channel, bustime_start, host='0.0.0.0', port=8004, backdoor_port=0,
no_authentication=False):
"""Thrimshim service."""
server = WSGIServer((host, port), cors(app))
app.no_authentication = no_authentication
app.default_channel = default_channel
app.bustime_start = bustime_start
stopping = gevent.event.Event()
def stop():

Loading…
Cancel
Save