Skip to content

Commit

Permalink
A bad implementation of a playqueue
Browse files Browse the repository at this point in the history
  • Loading branch information
iBicha committed Sep 6, 2023
1 parent ace9e2c commit 2e4a516
Show file tree
Hide file tree
Showing 18 changed files with 264 additions and 146 deletions.
44 changes: 14 additions & 30 deletions docs/playlet-web-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,36 +138,7 @@ paths:
responses:
"204":
description: No Content
/api/player/play:
post:
summary: Play video
description: Play a video. If a video is already playing, it will be stopped and the new video will be played.
operationId: playVideo
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/PlayVideoObject"
responses:
"204":
description: No Content
/api/player/pause:
post:
summary: Pause video
description: Pause the currently playing video.
operationId: pauseVideo
responses:
"204":
description: No Content
/api/player/resume:
post:
summary: Resume video
description: Resume the currently paused video.
operationId: resumeVideo
responses:
"204":
description: No Content
/api/player/queue:
/api/queue:
get:
summary: Get play queue
description: Get the current videos in the queue.
Expand Down Expand Up @@ -206,6 +177,19 @@ paths:
responses:
"204":
description: No Content
/api/queue/play:
post:
summary: Play video
description: Play a video. If a video is already playing, it will be stopped and the new video will be played.
operationId: playVideo
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/PlayVideoObject"
responses:
"204":
description: No Content
/api/playlet-lib-urls:
get:
summary: Get Playlet lib URLs
Expand Down
2 changes: 0 additions & 2 deletions playlet-lib/src/components/AppController/AppController.bs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ function PopScreen(unused as dynamic)
end function

function FocusTopScreen(unused as dynamic)
' TODO:P1 we should make screens remember which was the last focused child and restore it
' That would make a better transition between video node and the screen
childCount = m.stack.getChildCount()
topScreen = m.stack.getChild(childCount - 1)
NodeSetFocus(topScreen, true)
Expand Down
3 changes: 2 additions & 1 deletion playlet-lib/src/components/EcpArgs.bs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import "pkg:/components/VideoPlayer/VideoUtils.bs"
import "pkg:/source/utils/Types.bs"

function InitEcpArgs()
m.playQueue = m.top.findNode("PlayQueue")
LaunchArgumentsReceived()

m.scene.ObserveField("inputArgs", FuncName(InputArgumentsReceived))
Expand Down Expand Up @@ -44,6 +45,6 @@ function ProcessArguments(args as object) as void
if args.timestamp <> invalid
node.timestamp = args.timestamp
end if
VideoUtils.PlayVideo(node)
m.playQueue@.Play(node, -1)
end if
end function
3 changes: 2 additions & 1 deletion playlet-lib/src/components/MainScene.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
<Group id="Notifications" />

<!-- No render nodes -->
<PlayQueue id="PlayQueue" />
<PlayQueue id="PlayQueue"
invidious="bind:../Invidious" />
<ApplicationInfo id="ApplicationInfo" />
<Preferences id="Preferences" />

Expand Down
180 changes: 155 additions & 25 deletions playlet-lib/src/components/PlayQueue/PlayQueue.bs
Original file line number Diff line number Diff line change
@@ -1,41 +1,171 @@
import "pkg:/source/utils/Types.bs"
import "pkg:/components/VideoPlayer/VideoUtils.bs"
import "pkg:/source/asyncTask/asyncTask.bs"
import "pkg:/components/VideoFeed/FeedLoadState.bs"
import "pkg:/components/PlaylistView/PlaylistContentTask.bs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
import "pkg:/source/utils/ErrorUtils.bs"
import "pkg:/components/Dialog/DialogUtils.bs"
import "pkg:/source/utils/ArrayUtils.bs"

' TODO:P0 support content nodes
' TODO:P0 support playlists
' TODO:P0 support queing without playing
' TODO:P0 support playing by inserting into the current index
function Init()
m.top.queue = []
m.log = new log.Logger("PlayQueue")
m.pendingLoadTasks = {}
' TODO:P1 prevent adding duplicates of the same video/playlist
m.queue = CreateObject("roArray", 0, true)
end function

function Enqueue(items as object) as void
if IsArray(items)
for each item in items
Enqueue(item)
end for
function Play(node as object, playlistIndex as integer)
m.top.index += 1
m.top.playlistIndex = playlistIndex

if m.top.index >= m.queue.Count()
m.queue.push(node)
m.top.index = m.queue.Count() - 1
else
if not VideoUtils.IsVideoPlayerOpen()
VideoUtils.PlayVideo(items)
return
end if
queue = m.top.queue
queue.push(items)
m.top.queue = queue
ArrayUtils.Insert(m.queue, m.top.index, node)
end if

PlayItemAtCurrentIndex()
end function

function Dequeue(unused as dynamic) as object
queue = m.top.queue
value = queue.Shift()
m.top.queue = queue
return value
function AddToQueue(node as object)
m.queue.push(node)
end function

function Set(items as object)
m.top.queue = IsArray(items) ? items : [items]
' TODO:P1 better model of the queue (Now playing, etc)
function GetQueue(unused as dynamic) as object
items = []
for each item in m.queue
if item.type = "video"
items.push({
type: item.type,
videoId: item.videoId,
title: item.title,
author: item.author,
thumbnail: item.thumbnail
})
else if item.type = "playlist"
items.push({
type: item.type,
playlistId: item.playlistId,
title: item.title,
thumbnail: item.thumbnail,
videoCountText: item.videoCountText
})
end if
end for

return {
index: m.top.index,
playlistIndex: m.top.playlistIndex,
items: items
}
end function

function Clear(unused as dynamic)
m.top.queue = []
m.queue.Clear()
m.top.index = -1
m.top.playlistIndex = -1
end function

function PlayNextItem(unused as dynamic) as boolean
if m.queue.Count() = 0
return false
end if

if m.top.index <> -1
if m.top.index >= m.queue.Count()
return false
end if
node = m.queue[m.top.index]
if node.type = "playlist"
m.top.playlistIndex += 1
if PlayPlaylistAtIndex(node, m.top.playlistIndex)
return true
end if
end if
end if

m.top.index += 1
m.top.playlistIndex = -1
if m.top.index >= m.queue.Count()
return false
end if

return PlayItemAtCurrentIndex()
end function

function PlayItemAtCurrentIndex() as boolean
node = m.queue[m.top.index]
if node = invalid
return false
end if
if node.type = "video"
VideoUtils.PlayVideo(node)
return true
else if node.type = "playlist"
if m.top.playlistIndex = -1
m.top.playlistIndex = 0
end if
return PlayPlaylistAtIndex(node, m.top.playlistIndex)
end if
return false
end function

function PlayPlaylistAtIndex(playlist as object, index as integer) as boolean
' TODO:P0 if we only have the playlistId, videoCount would be zero, and we'd fail to load the playlist
if index < 0 or index >= playlist.videoCount
return false
end if

if index < playlist.getChildCount()
child = playlist.getChild(index)
VideoUtils.PlayVideo(child)
return true
end if

LoadPlaylist(playlist)
return true
end function

function LoadPlaylist(playlist as object) as void
if playlist.loadState = FeedLoadState.Loading
return
end if

playlist.loadState = FeedLoadState.Loading

task = StartAsyncTask(PlaylistContentTask, {
content: playlist,
invidious: m.top.invidious
}, OnPlaylistContentTaskResult)
m.pendingLoadTasks[task.id] = task
end function

function OnPlaylistContentTaskResult(output as object) as void
m.pendingLoadTasks.Delete(output.task.id)

if output.cancelled
return
end if

playlist = output.task.input.content
if not output.success or not output.result.success
' output.error for unhandled exception
error = output.error
if error = invalid
' output.result.error for network errors
error = output.result.error
end if
error = ErrorUtils.Format(error)
m.log.error(error)
playlistId = playlist.playlistId
message = `Failed to load playlist ${playlistId}\n${error}`
' TODO:P1 play next upon closing the dialog
DialogUtils.ShowDialog(message, "Playlist load fail", true)
return
end if

PlayPlaylistAtIndex(playlist, m.top.playlistIndex)
end function
11 changes: 7 additions & 4 deletions playlet-lib/src/components/PlayQueue/PlayQueue.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

<component name="PlayQueue" extends="Node">
<interface>
<!-- TODO:P0 nodearray -->
<field id="queue" type="array" />
<function name="Enqueue" />
<function name="Dequeue" />
<function name="Set" />
<field id="index" type="integer" value="-1" />
<field id="playlistIndex" type="integer" value="-1" />
<field id="invidious" type="node" />
<function name="Play" />
<function name="AddToQueue" />
<function name="GetQueue" />
<function name="Clear" />
<function name="PlayNextItem" />
</interface>
</component>
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import "pkg:/components/Services/Invidious/InvidiousService.bs"
import "pkg:/components/Services/Invidious/InvidiousToContentNode.bs"

' TODO:P1 this should only be called from PlaylistContentNode.bs
@asynctask
function PlaylistContentTask(input as object) as object
contentNode = input.content
invidiousNode = input.invidious

if m.top.cancel
contentNode.loadState = FeedLoadState.None
return invalid
end if

Expand All @@ -16,6 +18,7 @@ function PlaylistContentTask(input as object) as object
response = service.GetPlaylist(contentNode.playlistId, index, m.top.cancellation)

if m.top.cancel
contentNode.loadState = FeedLoadState.None
return invalid
end if

Expand Down
17 changes: 7 additions & 10 deletions playlet-lib/src/components/PlaylistView/PlaylistView.bs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import "pkg:/components/VideoFeed/FeedLoadState.bs"
import "pkg:/source/utils/ErrorUtils.bs"
import "pkg:/components/Dialog/DialogUtils.bs"

' TODO:P0 play video on select
' TODO:P2 use already loaded thumbnail, and crossfade to highres version (using same Poster)
' TODO:P0 Add playlist to the queue, and record index
function Init()
m.log = new log.Logger("PlaylistView")

Expand Down Expand Up @@ -85,14 +83,13 @@ end function

function OnItemSelected(event as object)
index = event.getData()
video = m.list.content.getChild(index)
playlist = m.list.content
playlistId = playlist.playlistId
video = playlist.getChild(index)
videoId = video.videoId
if not StringUtils.IsNullOrEmpty(videoId)
m.log.info("Play video: " + videoId)
if not VideoUtils.IsVideoPlayerOpen()
Close()
end if
VideoUtils.PlayVideo(video)
m.log.info("Play playlist: " + playlistId + " video: " + videoId)
m.playQueue@.Play(playlist, index)
end if
end function

Expand Down Expand Up @@ -148,8 +145,8 @@ function OnPlaylistContentTaskResult(output as object) as void
end if
error = ErrorUtils.Format(error)
m.log.error(error)
videoId = output.task.input.content.playlistId
message = `Failed to load playlist ${videoId}\n${error}`
playlistId = output.task.input.content.playlistId
message = `Failed to load playlist ${playlistId}\n${error}`
DialogUtils.ShowDialog(message, "Playlist load fail", true)
return
end if
Expand Down
1 change: 1 addition & 0 deletions playlet-lib/src/components/PlaylistView/PlaylistView.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<field id="content" type="node" onChange="OnContentSet" />
<field id="appController" type="node" bind="/AppController" />
<field id="invidious" type="node" bind="/Invidious" />
<field id="playQueue" type="node" bind="/PlayQueue" />
</interface>
<children>
<Rectangle
Expand Down
Loading

0 comments on commit 2e4a516

Please sign in to comment.