diff --git a/app/static/js/VideoElement.js b/app/static/js/VideoElement.js index 8238d95..9abe27d 100644 --- a/app/static/js/VideoElement.js +++ b/app/static/js/VideoElement.js @@ -8,6 +8,7 @@ VideoElement VideoElement Object loop loop playback playbackRate playback rate position start position + in start offset self Shared private variable ([options[, self]]) -> VideoElement Object loadedmetadata loadedmetadata @@ -38,6 +39,7 @@ window.VideoElement = function(options) { muted: false, playbackRate: 1, position: 0, + "in": 0, volume: 1 } Object.assign(self.options, options); @@ -166,9 +168,10 @@ window.VideoElement = function(options) { function getCurrentTime() { var item = self.items[self.currentItem]; - return self.seeking || self.loading + var currentTime = self.seeking || self.loading ? self.currentTime - : item ? item.position + self.video.currentTime - item['in'] : 0; + : item ? item.position + self.video.currentTime - item['in'] - self.options["in"] : 0; + return currentTime } function getset(key, value) { @@ -508,6 +511,7 @@ window.VideoElement = function(options) { } function setCurrentItemTime(currentTime) { + currentTime += self.options["in"] debug('Video', 'sCIT', currentTime, self.video.currentTime, 'delta', currentTime - self.video.currentTime); isReady(self.video, function(video) { diff --git a/app/static/js/VideoPlayer.js b/app/static/js/VideoPlayer.js index f478a53..b6e6ca5 100644 --- a/app/static/js/VideoPlayer.js +++ b/app/static/js/VideoPlayer.js @@ -10,6 +10,7 @@ window.VideoPlayer = function(options) { loop: false, muted: false, playbackRate: 1, + "in": 0, position: 0, volume: 1 } @@ -74,7 +75,6 @@ window.VideoPlayer = function(options) { height: 32px; } .mx-controls .controls .position { - cursor: pointer; flex: 1; } .mx-controls .toggle svg { @@ -154,9 +154,11 @@ window.VideoPlayer = function(options) { ${icon.mute}
-
-
- +
+ +
+
+
@@ -224,15 +226,31 @@ window.VideoPlayer = function(options) { } } var showControls + function hideControlsLater() { + if (showControls) { + clearTimeout(showControls) + } + showControls = setTimeout(() => { + if (touching) { + hideControlsLater() + } else { + self.controls.style.opacity = that.paused ? '1' : '0' + showControls = null + } + }, 3000) + } var toggleControls = event => { + if (event.target.tagName == "INPUT") { + if (showControls) { + clearTimeout(showControls) + } + return + } if (self.controls.style.opacity == '0') { event.preventDefault() event.stopPropagation() self.controls.style.opacity = '1' - showControls = setTimeout(() => { - self.controls.style.opacity = that.paused ? '1' : '0' - showControls = null - }, 3000) + hideControlsLater() } else { self.controls.style.opacity = '0' } @@ -242,10 +260,7 @@ window.VideoPlayer = function(options) { clearTimeout(showControls) } self.controls.style.opacity = '1' - showControls = setTimeout(() => { - self.controls.style.opacity = that.paused ? '1' : '0' - showControls = null - }, 3000) + hideControlsLater() }) self.controls.addEventListener("mouseleave", event => { if (showControls) { @@ -254,7 +269,13 @@ window.VideoPlayer = function(options) { self.controls.style.opacity = that.paused ? '1' : '0' showControls = null }) + self.controls.addEventListener("touchstart", event => { + touching = true + }) self.controls.addEventListener("touchstart", toggleControls) + self.controls.addEventListener("touchend", event => { + touching = false + }) self.controls.querySelector('.toggle').addEventListener("click", toggleVideo) self.controls.querySelector('.volume').addEventListener("click", toggleSound) self.controls.querySelector('.fullscreen-btn').addEventListener("click", toggleFullscreen) @@ -311,6 +332,7 @@ window.VideoPlayer = function(options) { that.append(unblock) }) var loading = true + var touching = false that.brightness(0) that.addEventListener("loadedmetadata", event => { // @@ -332,45 +354,40 @@ window.VideoPlayer = function(options) { } }) - var time = that.querySelector('.controls .time div'), - progress = that.querySelector('.controls .position .progress') - that.querySelector('.controls .position').addEventListener("click", event => { - var bar = event.target - if (bar && bar.classList.contains('position')) { - bar = bar.querySelector('.bar') - } - while (bar && !bar.classList.contains('bar')) { - bar = bar.parentElement - } - if (bar && bar.classList.contains('bar')) { - event.preventDefault() - event.stopPropagation() - var rect = bar.getBoundingClientRect() - var x = event.clientX - rect.x - var percent = x / rect.width - var position = percent * self.options.duration - if (self.options.position) { - position += self.options.position - } - progress.style.width = (100 * percent) + '%' - that.currentTime(position) - } + var time = that.querySelector('.controls .time div'); + const progressbar = that.querySelector('.seekbar div[role="progressbar"]'); + function setProgressPosition(value) { + progressbar.style.width = value + '%'; + progressbar.setAttribute('aria-valuenow', value); + + } + that.querySelector('.controls .position input').addEventListener('input', event => { + event.preventDefault() + event.stopPropagation() + setProgressPosition(event.target.value) + var position = event.target.value/100 * self.options.duration + displayTime(position) + that.currentTime(position) + hideControlsLater() }) - that.addEventListener("timeupdate", event => { - var currentTime = that.currentTime(), - duration = self.options.duration - if (self.options.position) { - currentTime -= self.options.position - } - progress.style.width = (100 * currentTime / duration) + '%' - duration = formatDuration(duration) + function displayTime(currentTime) { + duration = formatDuration(self.options.duration) currentTime = formatDuration(currentTime) while (duration && duration.startsWith('00:')) { duration = duration.slice(3) } currentTime = currentTime.slice(currentTime.length - duration.length) time.innerText = `${currentTime} / ${duration}` + } + that.addEventListener("timeupdate", event => { + var currentTime = that.currentTime(), + duration = self.options.duration + if (self.options.position) { + currentTime -= self.options.position + } + setProgressPosition(100 * currentTime / duration) + displayTime(currentTime) }) that.addEventListener("play", event => { diff --git a/app/static/js/edits.js b/app/static/js/edits.js index be9101e..f691d61 100644 --- a/app/static/js/edits.js +++ b/app/static/js/edits.js @@ -1,35 +1,4 @@ -const getSortValue = function(value) { - var sortValue = value; - function trim(value) { - return value.replace(/^\W+(?=\w)/, ''); - } - if ( - isEmpty(value) - || isNull(value) - || isUndefined(value) - ) { - sortValue = null; - } else if (isString(value)) { - // make lowercase and remove leading non-word characters - sortValue = trim(value.toLowerCase()); - // move leading articles to the end - // and remove leading non-word characters - ['a', 'an', 'the'].forEach(function(article) { - if (new RegExp('^' + article + ' ').test(sortValue)) { - sortValue = trim(sortValue.slice(article.length + 1)) - + ', ' + sortValue.slice(0, article.length); - return false; // break - } - }); - // remove thousand separators and pad numbers - sortValue = sortValue.replace(/(\d),(?=(\d{3}))/g, '$1') - .replace(/\d+/g, function(match) { - return match.padStart(64, '0') - }); - } - return sortValue; -}; const sortByKey = function(array, by) { return array.sort(function(a, b) { @@ -122,7 +91,7 @@ async function loadEdit(id, args) { } } data.edit = response['data'] - if (data.edit.status !== 'public') { + if (['public', 'featured'].indexOf(data.edit.status) == -1) { return { site: data.site, error: { diff --git a/app/static/js/item.js b/app/static/js/item.js index 0cc3dcf..42c4d2c 100644 --- a/app/static/js/item.js +++ b/app/static/js/item.js @@ -129,6 +129,10 @@ async function loadData(id, args) { ${icon.down} ${layerData.title} `) + data.layers[layer] = sortBy(data.layers[layer], [ + {key: "in", operator: "+"}, + {key: "created", operator: "+"} + ]) data.layers[layer].forEach(annotation => { if (pandora.url) { annotation.value = annotation.value.replace( @@ -137,9 +141,13 @@ async function loadData(id, args) { /href="\//g, `href="${pandora.url.origin}/` ) } + let content = annotation.value + if (!layerData.isSubtitles && layerData.type == "text" && args.show && args.show.includes("user")) { + content += `\n
— ${annotation.user}
` + } html.push(`
- ${annotation.value} + ${content}
`) }) diff --git a/app/static/js/main.js b/app/static/js/main.js index ebd9b81..421eacc 100644 --- a/app/static/js/main.js +++ b/app/static/js/main.js @@ -14,7 +14,7 @@ function parseURL() { var kv = arg.split('=') k = kv.shift() v = kv.join('=') - if (['users', 'layers'].includes(k)) { + if (['users', 'layers', 'show'].includes(k)) { v = v.split(',') } return [k, v] @@ -67,6 +67,7 @@ function parseURL() { id = id.replace('/editor/', '/').replace('/player/', '/') type = "item" } + //console.log(type, id, args) return [type, id, args] } diff --git a/app/static/js/render.js b/app/static/js/render.js index 307c5d2..40422e5 100644 --- a/app/static/js/render.js +++ b/app/static/js/render.js @@ -75,7 +75,8 @@ function renderItem(data) { var video = window.video = VideoPlayer({ items: data.videos, poster: data.poster, - position: data["in"] || 0, + "in": data["in"] || 0, + position: 0, duration: data.duration, aspectratio: data.aspectratio }) @@ -85,16 +86,10 @@ function renderItem(data) { video.addEventListener("loadedmetadata", event => { // }) - video.addEventListener("timeupdate", event => { - var currentTime = video.currentTime() - if (currentTime >= data['out']) { - if (!video.paused) { - video.pause() - } - video.currentTime(data['in']) - } + + function updateAnnotations(currentTime) { div.querySelectorAll('.annotation').forEach(annot => { - var now = currentTime + var now = currentTime + (data["in"] || 0) var start = parseFloat(annot.dataset.in) var end = parseFloat(annot.dataset.out) if (now >= start && now <= end) { @@ -107,8 +102,18 @@ function renderItem(data) { } } }) - + } + video.addEventListener("timeupdate", event => { + var currentTime = video.currentTime() + if ((currentTime + (data["in"] || 0)) >= data['out']) { + if (!video.paused) { + video.pause() + } + video.currentTime(0) + } + updateAnnotations(currentTime) }) + updateAnnotations(data["position"] || 0) if (item.next || item.previous) { var nav = document.createElement('nav') nav.classList.add('items') diff --git a/app/static/js/utils.js b/app/static/js/utils.js index cbaec6a..92d6c80 100644 --- a/app/static/js/utils.js +++ b/app/static/js/utils.js @@ -125,7 +125,10 @@ const clickLink = function(event) { } document.location.hash = '#' + link.slice(1) } else { - if (!link.startsWith('/m')) { + if (link.includes('/download/')) { + document.location.href = link + return + } else if (!link.startsWith('/m')) { link = '/m' + link } history.pushState({}, '', link); @@ -161,3 +164,59 @@ const getVideoURL = function(id, resolution, part, track, streamId) { return prefix + '/' + getVideoURLName(id, resolution, part, track, streamId); }; +const getSortValue = function(value) { + var sortValue = value; + function trim(value) { + return value.replace(/^\W+(?=\w)/, ''); + } + if ( + isEmpty(value) + || isNull(value) + || isUndefined(value) + ) { + sortValue = null; + } else if (isString(value)) { + // make lowercase and remove leading non-word characters + sortValue = trim(value.toLowerCase()); + // move leading articles to the end + // and remove leading non-word characters + ['a', 'an', 'the'].forEach(function(article) { + if (new RegExp('^' + article + ' ').test(sortValue)) { + sortValue = trim(sortValue.slice(article.length + 1)) + + ', ' + sortValue.slice(0, article.length); + return false; // break + } + }); + // remove thousand separators and pad numbers + sortValue = sortValue.replace(/(\d),(?=(\d{3}))/g, '$1') + .replace(/\d+/g, function(match) { + return match.padStart(64, '0') + }); + } + return sortValue; +}; + +function sortBy(array, by, map) { + return array.sort(function(a, b) { + var aValue, bValue, index = 0, key, ret = 0; + while (ret == 0 && index < by.length) { + key = by[index].key; + aValue = getSortValue( + map && map[key] ? map[key](a[key], a) : a[key] + ); + bValue = getSortValue( + map && map[key] ? map[key](b[key], b) : b[key] + ); + if ((aValue === null) != (bValue === null)) { + ret = aValue === null ? 1 : -1; + } else if (aValue < bValue) { + ret = by[index].operator == '+' ? -1 : 1; + } else if (aValue > bValue) { + ret = by[index].operator == '+' ? 1 : -1; + } else { + index++; + } + } + return ret; + }); +}