mirror of
https://git.sr.ht/~cadence/cloudtube
synced 2024-11-25 17:07:30 +00:00
Implement continuous mode
This commit is contained in:
parent
e4f6ffe122
commit
2b2f8bf84a
30
api/video.js
30
api/video.js
@ -183,21 +183,35 @@ module.exports = [
|
|||||||
const user = getUser(req)
|
const user = getUser(req)
|
||||||
const settings = user.getSettingsOrDefaults()
|
const settings = user.getSettingsOrDefaults()
|
||||||
const id = url.searchParams.get("v")
|
const id = url.searchParams.get("v")
|
||||||
|
|
||||||
|
// Media fragment
|
||||||
const t = url.searchParams.get("t")
|
const t = url.searchParams.get("t")
|
||||||
let mediaFragment = converters.tToMediaFragment(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 (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})
|
return render(200, "pug/local-video.pug", {id})
|
||||||
}
|
}
|
||||||
const instanceOrigin = settings.instance
|
var instanceOrigin = settings.instance
|
||||||
const outURL = `${instanceOrigin}/api/v1/videos/${id}`
|
var outURL = `${instanceOrigin}/api/v1/videos/${id}`
|
||||||
const video = await request(outURL).then(res => res.json())
|
var video = await request(outURL).then(res => res.json())
|
||||||
return renderVideo(video, {user, settings, id, instanceOrigin}, {mediaFragment})
|
|
||||||
} else { // req.method === "POST"
|
} else { // req.method === "POST"
|
||||||
const video = JSON.parse(new URLSearchParams(body.toString()).get("video"))
|
var instanceOrigin = "http://localhost:3000"
|
||||||
const instanceOrigin = "http://localhost:3000"
|
var video = JSON.parse(new URLSearchParams(body.toString()).get("video"))
|
||||||
return renderVideo(video, {user, settings, id, instanceOrigin}, {mediaFragment})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return renderVideo(video, {
|
||||||
|
user, settings, id, instanceOrigin
|
||||||
|
}, {
|
||||||
|
mediaFragment, continuous, sessionWatched, sessionWatchedNext
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
34
html/static/js/continuous.js
Normal file
34
html/static/js/continuous.js
Normal file
@ -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"))
|
@ -1,6 +1,8 @@
|
|||||||
mixin video_list_item(className, video, instanceOrigin, options = {})
|
mixin video_list_item(className, video, instanceOrigin, options = {})
|
||||||
div(class={[className]: true, "video-list-item--watched": video.watched})
|
div(class={[className]: true, "video-list-item--watched": video.watched})
|
||||||
- let link = `/watch?v=${video.videoId}`
|
- let link = `/watch?v=${video.videoId}`
|
||||||
|
if options.continuous
|
||||||
|
- link += `&continuous=1&session-watched=${sessionWatchedNext}`
|
||||||
a(href=link tabindex="-1").thumbnail
|
a(href=link tabindex="-1").thumbnail
|
||||||
img(src=`/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image
|
img(src=`/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image
|
||||||
if video.second__lengthText != undefined
|
if video.second__lengthText != undefined
|
||||||
|
@ -9,16 +9,22 @@ block head
|
|||||||
else
|
else
|
||||||
title Error - CloudTube
|
title Error - CloudTube
|
||||||
script(type="module" src=getStaticURL("html", "/static/js/player.js"))
|
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
|
block content
|
||||||
unless error
|
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-page
|
||||||
.main-video-section
|
.main-video-section
|
||||||
.video-container
|
.video-container
|
||||||
- const format = formats[0]
|
- const format = formats[0]
|
||||||
if format
|
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)
|
source(src=format.url+mediaFragment type=format.type)
|
||||||
each t in video.captions
|
each t in video.captions
|
||||||
track(label=t.label kind="subtitles" srclang=t.languageCode src=t.url)
|
track(label=t.label kind="subtitles" srclang=t.languageCode src=t.url)
|
||||||
@ -44,6 +50,15 @@ block content
|
|||||||
#live-event-notice
|
#live-event-notice
|
||||||
#audio-loading-display
|
#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
|
.button-container
|
||||||
+subscribe_button(video.authorId, subscribed, `/watch?v=${video.videoId}`).border-look
|
+subscribe_button(video.authorId, subscribed, `/watch?v=${video.videoId}`).border-look
|
||||||
//- button.border-look#theatre Theatre
|
//- button.border-look#theatre Theatre
|
||||||
@ -60,11 +75,29 @@ block content
|
|||||||
|
|
||||||
.description!= video.descriptionHtml
|
.description!= video.descriptionHtml
|
||||||
|
|
||||||
aside.related-videos
|
//- Standard view
|
||||||
|
aside(style=continuous ? "display: none" : "")#standard-related-videos.related-videos
|
||||||
|
.related-cols
|
||||||
h2.related-header Related videos
|
h2.related-header Related videos
|
||||||
|
.continuous-start
|
||||||
|
a(href=`/watch?v=${video.videoId}&continuous=1` nofollow) Continuous mode
|
||||||
each r in video.recommendedVideos
|
each r in video.recommendedVideos
|
||||||
+video_list_item("related-video", r, instanceOrigin)
|
+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
|
else
|
||||||
//- error
|
//- error
|
||||||
main.video-error-page
|
main.video-error-page
|
||||||
|
@ -8,6 +8,7 @@ $bg-accent-area: #44474b
|
|||||||
$fg-bright: #fff
|
$fg-bright: #fff
|
||||||
$fg-main: #ddd
|
$fg-main: #ddd
|
||||||
$fg-dim: #bbb
|
$fg-dim: #bbb
|
||||||
|
$fg-warning: #ffb98f
|
||||||
|
|
||||||
$edge-grey: #808080
|
$edge-grey: #808080
|
||||||
|
|
||||||
|
@ -69,6 +69,29 @@
|
|||||||
border-radius: 3px
|
border-radius: 3px
|
||||||
background: linear-gradient(to right, #1a1 var(--rating), #bbb var(--rating))
|
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
|
.description
|
||||||
font-size: 17px
|
font-size: 17px
|
||||||
word-break: break-word
|
word-break: break-word
|
||||||
@ -80,8 +103,17 @@
|
|||||||
.subscribe-form
|
.subscribe-form
|
||||||
display: inline-block
|
display: inline-block
|
||||||
|
|
||||||
|
.related-cols
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: space-between
|
||||||
|
margin-bottom: 8px
|
||||||
|
|
||||||
|
.continuous-start
|
||||||
|
font-size: 15px
|
||||||
|
|
||||||
.related-header
|
.related-header
|
||||||
margin: 4px 0px 12px 2px
|
margin: 4px 0px 4px 2px
|
||||||
font-weight: normal
|
font-weight: normal
|
||||||
font-size: 26px
|
font-size: 26px
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ let constants = {
|
|||||||
user_settings: {
|
user_settings: {
|
||||||
instance: {
|
instance: {
|
||||||
type: "string",
|
type: "string",
|
||||||
default: "https://second.cadence.moe"
|
default: "http://localhost:3000"
|
||||||
},
|
},
|
||||||
save_history: {
|
save_history: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
|
Loading…
Reference in New Issue
Block a user