mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2025-09-05 10:12:46 +00:00
Report playback progress and resume items
[Playback]: New: playback progress is reported to the Jellyfin server. [Playback]: New: resume partly played items or start playing from the beginning if desired. I also had to make some changes to the VideoPlayer, because the VideoHUD got locked up when the player changed status from Buffering to Buffered too quickly in succession, which occurs when trying to seek directly after the application is able to.
This commit is contained in:
parent
7221fda1d5
commit
c01fcdcb54
13 changed files with 195 additions and 27 deletions
|
@ -33,6 +33,14 @@ function timeToText(time) {
|
|||
return hours + ":" + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "")+ seconds
|
||||
}
|
||||
|
||||
function msToTicks(ms) {
|
||||
return ms * 10000;
|
||||
}
|
||||
|
||||
function ticksToMs(ticks) {
|
||||
return ticks / 10000;
|
||||
}
|
||||
|
||||
function ticksToText(ticks) {
|
||||
return timeToText(ticks / 10000);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ Rectangle {
|
|||
anchors.fill: backgroundImage
|
||||
source: backgroundImage
|
||||
opacity: dimmedOpacity
|
||||
radius: 50
|
||||
radius: 100
|
||||
}
|
||||
|
||||
Image {
|
||||
|
|
|
@ -23,7 +23,8 @@ import Sailfish.Silica 1.0
|
|||
Column {
|
||||
property alias imageSource : playImage.source
|
||||
property real imageAspectRatio: 1.0
|
||||
signal playPressed()
|
||||
property real playProgress: 0.0
|
||||
signal playPressed(bool startFromBeginning)
|
||||
spacing: Theme.paddingLarge
|
||||
|
||||
BackgroundItem {
|
||||
|
@ -42,7 +43,16 @@ Column {
|
|||
anchors.centerIn: parent
|
||||
highlighted: parent.highlighted
|
||||
}
|
||||
onClicked: playPressed()
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: Theme.paddingMedium
|
||||
color: Theme.highlightColor
|
||||
width: parent.width * playProgress
|
||||
}
|
||||
onClicked: playPressed(false)
|
||||
}
|
||||
Row {
|
||||
anchors {
|
||||
|
@ -52,6 +62,12 @@ Column {
|
|||
rightMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
spacing: Theme.paddingMedium
|
||||
IconButton {
|
||||
id: playFromBeginning
|
||||
icon.source: "image://theme/icon-m-backup"
|
||||
visible: playProgress > 0
|
||||
onClicked: playPressed(true)
|
||||
}
|
||||
IconButton {
|
||||
id: favouriteButton
|
||||
icon.source: "image://theme/icon-m-favorite"
|
||||
|
|
|
@ -23,6 +23,7 @@ import Sailfish.Silica 1.0
|
|||
import nl.netsoj.chris.Jellyfin 1.0
|
||||
|
||||
import "videoplayer"
|
||||
import "../"
|
||||
|
||||
/**
|
||||
* A videoPlayer for Jellyfin videos
|
||||
|
@ -38,6 +39,7 @@ SilicaItem {
|
|||
readonly property bool hudVisible: !hud.hidden || player.error !== MediaPlayer.NoError
|
||||
property alias audioTrack: mediaSource.audioIndex
|
||||
property alias subtitleTrack: mediaSource.subtitleIndex
|
||||
property int startTicks: 0
|
||||
|
||||
// Force a Light on Dark theme since I doubt that there are persons who are willing to watch a Video
|
||||
// on a white background.
|
||||
|
@ -49,7 +51,6 @@ SilicaItem {
|
|||
color: "black"
|
||||
}
|
||||
|
||||
|
||||
MediaSource {
|
||||
id: mediaSource
|
||||
apiClient: ApiClient
|
||||
|
@ -59,11 +60,16 @@ SilicaItem {
|
|||
onStreamUrlChanged: {
|
||||
if (mediaSource.streamUrl != "") {
|
||||
player.source = streamUrl
|
||||
//mediaPlayer.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: player
|
||||
onPlaybackStateChanged: mediaSource.state = player.playbackState
|
||||
onPositionChanged: mediaSource.position = Utils.msToTicks(player.position)
|
||||
}
|
||||
|
||||
|
||||
VideoOutput {
|
||||
id: videoOutput
|
||||
|
@ -99,6 +105,18 @@ SilicaItem {
|
|||
|
||||
function stop() {
|
||||
player.stop()
|
||||
player.source = ""
|
||||
//player.source = ""
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: playerReadyToSeek
|
||||
target: player
|
||||
onPlaybackStateChanged: {
|
||||
if (startTicks > 0 && player.playbackState == MediaPlayer.PlayingState) {
|
||||
console.log("Seeking to " + Utils.ticksToMs(startTicks))
|
||||
player.seek(Utils.ticksToMs(startTicks))
|
||||
playerReadyToSeek.enabled = false // Only seek the first time this property changes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,7 @@ Column {
|
|||
onTracksChanged: {
|
||||
audioModel.clear()
|
||||
subtitleModel.clear()
|
||||
if (typeof tracks === "undefined") return
|
||||
for(var i = 0; i < tracks.length; i++) {
|
||||
var track = tracks[i];
|
||||
switch(track.Type) {
|
||||
|
|
|
@ -41,6 +41,10 @@ Rectangle {
|
|||
color: Theme.errorColor
|
||||
text: {
|
||||
switch(player.error) {
|
||||
case MediaPlayer.NoError:
|
||||
//: Just to be complete if the application shows a video playback error when there's no error.
|
||||
qsTr("No error");
|
||||
break;
|
||||
case MediaPlayer.ResourceError:
|
||||
//: Video playback error: out of resources
|
||||
qsTr("Resource allocation error")
|
||||
|
|
|
@ -65,7 +65,11 @@ Item {
|
|||
id: wakeupArea
|
||||
enabled: true
|
||||
anchors.fill: parent
|
||||
onClicked: hidden ? videoHud.show(true) : videoHud.hide(true)
|
||||
onClicked: {
|
||||
hidden ? videoHud.show(true) : videoHud.hide(true)
|
||||
console.log("Trying")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
|
@ -156,18 +160,21 @@ Item {
|
|||
}
|
||||
|
||||
function show(manual) {
|
||||
_manuallyActivated = manual
|
||||
if (manual) {
|
||||
_manuallyActivated = true
|
||||
inactivityTimer.restart()
|
||||
} else {
|
||||
_manuallyActivated = false
|
||||
inactivityTimer.stop()
|
||||
}
|
||||
opacity = 1
|
||||
}
|
||||
|
||||
function hide(manual) {
|
||||
// Don't hide if the user decided on their own to show the hud
|
||||
if (!manual && _manuallyActivated) return;
|
||||
//if (!manual && _manuallyActivated) return;
|
||||
// Don't give in to the user if they want to hide the hud while it was forced upon them
|
||||
/*if (!_manuallyActivated && manual) return;
|
||||
_manuallyActivated = false;*/
|
||||
opacity = 0
|
||||
}
|
||||
|
||||
|
|
|
@ -199,7 +199,7 @@ Page {
|
|||
+ "/Images/Primary?maxHeight=" + height + "&tag=" + model.imageTags["Primary"]
|
||||
: ""*/
|
||||
landscape: !Utils.usePortraitCover(model.type)
|
||||
progress: model.userData.PlayedPercentage / 100
|
||||
progress: (typeof model.userData !== "undefined") ? model.userData.PlayedPercentage / 100 : 0.0
|
||||
|
||||
onClicked: {
|
||||
pageStack.push(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.id})
|
||||
|
|
|
@ -33,6 +33,7 @@ Page {
|
|||
property var itemData
|
||||
property int audioTrack
|
||||
property int subtitleTrack
|
||||
property int startTicks: 0
|
||||
|
||||
allowedOrientations: Orientation.All
|
||||
showNavigationIndicator: videoPlayer.hudVisible
|
||||
|
@ -45,6 +46,7 @@ Page {
|
|||
title: itemData.Name
|
||||
audioTrack: videoPage.audioTrack
|
||||
subtitleTrack: videoPage.subtitleTrack
|
||||
startTicks: videoPage.startTicks
|
||||
|
||||
onLandscapeChanged: {
|
||||
console.log("Is landscape: " + landscape)
|
||||
|
|
|
@ -47,10 +47,14 @@ BaseDetailPage {
|
|||
|
||||
PlayToolbar {
|
||||
imageSource: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})
|
||||
imageAspectRatio: itemData.PrimaryImageAspectRatio
|
||||
imageAspectRatio: itemData.PrimaryImageAspectRatio || 1.0
|
||||
playProgress: itemData.UserData.PlayedPercentage / 100
|
||||
onPlayPressed: pageStack.push(Qt.resolvedUrl("../VideoPage.qml"),
|
||||
{"itemId": itemId, "itemData": itemData, "audioTrack": trackSelector.audioTrack,
|
||||
"subtitleTrack": trackSelector.subtitleTrack })
|
||||
{"itemId": itemId, "itemData": itemData,
|
||||
"audioTrack": trackSelector.audioTrack,
|
||||
"subtitleTrack": trackSelector.subtitleTrack,
|
||||
"startTicks": startFromBeginning ? 0.0
|
||||
: itemData.UserData.PlaybackPositionTicks })
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
|
@ -66,7 +70,7 @@ BaseDetailPage {
|
|||
|
||||
PlainLabel {
|
||||
id: overviewText
|
||||
text: itemData.Overview
|
||||
text: itemData.Overview || qsTr("No overview available")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.secondaryHighlightColor
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue