diff --git a/api/video.js b/api/video.js index 1e0d19b..ad82499 100644 --- a/api/video.js +++ b/api/video.js @@ -183,21 +183,35 @@ module.exports = [ const user = getUser(req) const settings = user.getSettingsOrDefaults() const id = url.searchParams.get("v") + + // Media fragment const t = url.searchParams.get("t") let mediaFragment = converters.tToMediaFragment(t) + + // Continuous mode + const continuous = url.searchParams.get("continuous") === "1" + const swp = url.searchParams.get("session-watched") + const sessionWatched = swp ? swp.split(" ") : [] + const sessionWatchedNext = sessionWatched.concat([id]).join("+") + if (continuous) settings.quality = 0 // autoplay with synced streams does not work + if (req.method === "GET") { - if (settings.local) { + if (settings.local) { // skip to the local fetching page, which will then POST video data in a moment return render(200, "pug/local-video.pug", {id}) } - const instanceOrigin = settings.instance - const outURL = `${instanceOrigin}/api/v1/videos/${id}` - const video = await request(outURL).then(res => res.json()) - return renderVideo(video, {user, settings, id, instanceOrigin}, {mediaFragment}) + var instanceOrigin = settings.instance + var outURL = `${instanceOrigin}/api/v1/videos/${id}` + var video = await request(outURL).then(res => res.json()) } else { // req.method === "POST" - const video = JSON.parse(new URLSearchParams(body.toString()).get("video")) - const instanceOrigin = "http://localhost:3000" - return renderVideo(video, {user, settings, id, instanceOrigin}, {mediaFragment}) + var instanceOrigin = "http://localhost:3000" + var video = JSON.parse(new URLSearchParams(body.toString()).get("video")) } + + return renderVideo(video, { + user, settings, id, instanceOrigin + }, { + mediaFragment, continuous, sessionWatched, sessionWatchedNext + }) } } ] diff --git a/html/static/js/continuous.js b/html/static/js/continuous.js new file mode 100644 index 0000000..dfbcd30 --- /dev/null +++ b/html/static/js/continuous.js @@ -0,0 +1,34 @@ +import {q, ejs, ElemJS} from "/static/js/elemjs/elemjs.js" + +const video = q("#video") + +console.log + +video.addEventListener("ended", () => { + if (data.continuous) { + const first = data.recommendedVideos[0] + window.location.assign(`/watch?v=${first.videoId}&continuous=1`) + } +}) + +class ContinuousControls extends ElemJS { + constructor() { + super(q("#continuous-controls")) + this.button = ejs(q("#continuous-stop")) + this.button.on("click", this.onClick.bind(this)) + } + + onClick(event) { + event.preventDefault() + this.element.style.display = "none" + data.continuous = false + q("#continuous-related-videos").remove() + q("#standard-related-videos").style.display = "" + const url = new URL(location) + url.searchParams.delete("continuous") + url.searchParams.delete("session-watched") + history.replaceState(null, "", url.toString()) + } +} + +new ContinuousControls(q("#continuous-stop")) diff --git a/pug/includes/video-list-item.pug b/pug/includes/video-list-item.pug index 4cd4b86..3a3fa81 100644 --- a/pug/includes/video-list-item.pug +++ b/pug/includes/video-list-item.pug @@ -1,6 +1,8 @@ mixin video_list_item(className, video, instanceOrigin, options = {}) div(class={[className]: true, "video-list-item--watched": video.watched}) - let link = `/watch?v=${video.videoId}` + if options.continuous + - link += `&continuous=1&session-watched=${sessionWatchedNext}` a(href=link tabindex="-1").thumbnail img(src=`/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image if video.second__lengthText != undefined diff --git a/pug/video.pug b/pug/video.pug index abb4a8e..804d7b9 100644 --- a/pug/video.pug +++ b/pug/video.pug @@ -9,16 +9,22 @@ block head else title Error - CloudTube script(type="module" src=getStaticURL("html", "/static/js/player.js")) - script const data = !{JSON.stringify(video)} + script const data = !{JSON.stringify({...video, continuous})} block content unless error + if continuous + - const first = video.recommendedVideos[0] + if first + script(type="module" src=getStaticURL("html", "/static/js/continuous.js")) + noscript + meta(http-equiv="refresh" content=`${video.lengthSeconds+5};url=/watch?v=${first.videoId}&continuous=1`) main.video-page .main-video-section .video-container - const format = formats[0] if format - video(controls preload="auto" width=format.second__width height=format.second__height data-itag=format.itag)#video.video + video(controls preload="auto" width=format.second__width height=format.second__height data-itag=format.itag autoplay=continuous)#video.video source(src=format.url+mediaFragment type=format.type) each t in video.captions track(label=t.label kind="subtitles" srclang=t.languageCode src=t.url) @@ -44,6 +50,15 @@ block content #live-event-notice #audio-loading-display + if continuous + div#continuous-controls.continuous + .continuous__description + .continuous__title Continuous mode: next video autoplays + noscript + .continuous__script-warning Without JavaScript, it will trigger on a timer, not on video completion. + .continuous__buttons + a(href=`/watch?v=${video.videoId}`)#continuous-stop.border-look Turn off + .button-container +subscribe_button(video.authorId, subscribed, `/watch?v=${video.videoId}`).border-look //- button.border-look#theatre Theatre @@ -60,11 +75,29 @@ block content .description!= video.descriptionHtml - aside.related-videos - h2.related-header Related videos + //- Standard view + aside(style=continuous ? "display: none" : "")#standard-related-videos.related-videos + .related-cols + h2.related-header Related videos + .continuous-start + a(href=`/watch?v=${video.videoId}&continuous=1` nofollow) Continuous mode each r in video.recommendedVideos +video_list_item("related-video", r, instanceOrigin) + //- Continuous view + if continuous + aside.related-videos#continuous-related-videos + - let column = video.recommendedVideos.filter(v => !sessionWatched.includes(v.videoId)) + if column.length + .related-cols + h2.related-header Autoplay next + +video_list_item("related-video", column.shift(), instanceOrigin, {continuous: true}) + if column.length + .related-cols + h2.related-header Related videos + each r in column + +video_list_item("related-video", r, instanceOrigin, {continuous: true}) // keep on continuous mode for all recommendations + else //- error main.video-error-page diff --git a/sass/includes/colors.sass b/sass/includes/colors.sass index 63a9903..6f5c830 100644 --- a/sass/includes/colors.sass +++ b/sass/includes/colors.sass @@ -8,6 +8,7 @@ $bg-accent-area: #44474b $fg-bright: #fff $fg-main: #ddd $fg-dim: #bbb +$fg-warning: #ffb98f $edge-grey: #808080 diff --git a/sass/includes/video-page.sass b/sass/includes/video-page.sass index 0459983..5b99428 100644 --- a/sass/includes/video-page.sass +++ b/sass/includes/video-page.sass @@ -69,6 +69,29 @@ border-radius: 3px background: linear-gradient(to right, #1a1 var(--rating), #bbb var(--rating)) + .continuous + display: flex + align-items: center + justify-content: space-between + margin: 16px 4px + padding: 12px + border-radius: 4px + background-color: c.$bg-darkest + + &__description + margin-left: 12px + + &__title + font-size: 20px + + &__script-warning + font-size: 15px + color: c.$fg-warning + + &__buttons + display: flex + flex-shrink: 0 + .description font-size: 17px word-break: break-word @@ -80,8 +103,17 @@ .subscribe-form display: inline-block +.related-cols + display: flex + align-items: center + justify-content: space-between + margin-bottom: 8px + +.continuous-start + font-size: 15px + .related-header - margin: 4px 0px 12px 2px + margin: 4px 0px 4px 2px font-weight: normal font-size: 26px diff --git a/utils/constants.js b/utils/constants.js index 6d9a92c..7e71f81 100644 --- a/utils/constants.js +++ b/utils/constants.js @@ -7,7 +7,7 @@ let constants = { user_settings: { instance: { type: "string", - default: "https://second.cadence.moe" + default: "http://localhost:3000" }, save_history: { type: "boolean",