1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2025-09-05 10:12:46 +00:00

Added videoplayer and many unrelated things

This commit is contained in:
Chris Josten 2020-09-25 14:46:39 +02:00
parent 53b3eac213
commit 92a18c4fa5
28 changed files with 889 additions and 51 deletions

View file

@ -71,6 +71,6 @@ Item {
right: parent.right
}
width: parent.width
height: children[0].height
height: children.length > 0 ? children[0].height : 0
}
}

View file

@ -17,13 +17,13 @@ Image {
GradientStop { position: 0.0; color: Theme.highlightColor; }
GradientStop { position: 1.0; color: Theme.highlightDimmerColor; }
}
visible: parent.status == Image.Error
visible: parent.status == Image.Error || parent.status == Image.Null
}
Image {
id: fallbackImageItem
anchors.centerIn: parent
visible: parent.status == Image.Error
visible: parent.status == Image.Error || parent.status == Image.Null
source: fallbackImage ? fallbackImage : "image://theme/icon-m-question"
}
}

View file

@ -0,0 +1,64 @@
import QtQuick 2.6
import QtMultimedia 5.6
import Sailfish.Silica 1.0
import nl.netsoj.chris.Jellyfin 1.0
import "videoplayer"
Item {
id: playerRoot
property string itemId
property string title
property int progress
readonly property bool landscape: videoOutput.contentRect.width > videoOutput.contentRect.height
property MediaPlayer player
readonly property bool hudVisible: !hud.hidden
MediaSource {
id: mediaSource
apiClient: ApiClient
itemId: playerRoot.itemId
autoOpen: true
//autoPlay: true
onStreamUrlChanged: {
if (mediaSource.streamUrl != "") {
player.source = streamUrl
//mediaPlayer.play()
}
}
}
VideoOutput {
id: videoOutput
source: player
anchors.fill: parent
}
VideoHud {
id: hud
anchors.fill: parent
player: playerRoot.player
title: videoPlayer.title
Label {
anchors.fill: parent
anchors.margins: Theme.horizontalPageMargin
text: itemId + "\n" + mediaSource.streamUrl + "\n"
+ player.status + "\n"
+ player.bufferProgress + "\n"
+ player.metaData.videoCodec + "@" + player.metaData.videoFrameRate + "(" + player.metaData.videoBitRate + ")" + "\n"
+ player.metaData.audioCodec + "(" + player.metaData.audioBitRate + ")" + "\n"
+ player.errorString + "\n"
font.pixelSize: Theme.fontSizeExtraSmall
wrapMode: "WordWrap"
visible: false
}
}
function stop() {
player.stop()
player.source = ""
}
}

View file

@ -0,0 +1,5 @@
import QtQuick 2.0
Item {
}

View file

@ -0,0 +1,5 @@
import QtQuick 2.0
Item {
}

View file

@ -0,0 +1,166 @@
import QtQuick 2.6
import QtMultimedia 5.6
import Sailfish.Silica 1.0
Item {
id: videoHud
property MediaPlayer player
property string title
property bool _manuallyActivated: false
readonly property bool hidden: opacity == 0.0
Behavior on opacity { FadeAnimator {} }
Rectangle {
id: topBar
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: pageTitle.height
gradient: Gradient {
GradientStop { position: 1.0; color: Theme.rgba(palette.overlayBackgroundColor, 0.15); }
GradientStop { position: 0.0; color: Theme.rgba(palette.overlayBackgroundColor, 0.30); }
}
PageHeader {
id: pageTitle
title: videoHud.title
anchors.fill: parent
titleColor: palette.primaryColor
}
}
Rectangle {
anchors.top: topBar.bottom
anchors.bottom: bottomBar.top
anchors.left: parent.left
anchors.right: parent.right
color: Theme.rgba(palette.overlayBackgroundColor, 0.15)
}
MouseArea {
id: wakeupArea
enabled: true
anchors.fill: parent
onClicked: hidden ? videoHud.show(true) : videoHud.hide(true)
}
BusyIndicator {
id: busyIndicator
anchors.centerIn: parent
size: BusyIndicatorSize.Medium
running: [MediaPlayer.Loading, MediaPlayer.Stalled].indexOf(player.status) >= 0
}
IconButton {
id: playPause
enabled: !hidden
anchors.centerIn: parent
icon.source: player.playbackState == MediaPlayer.PausedState ? "image://theme/icon-l-play" : "image://theme/icon-l-pause"
onClicked: {
if (player.playbackState == MediaPlayer.PlayingState) {
player.pause()
} else {
player.play()
}
}
visible: !busyIndicator.running
}
Rectangle {
id: bottomBar
anchors.bottom: parent.bottom
width: parent.width
height: progress.height
visible: [MediaPlayer.Unavailable, MediaPlayer.Loading, MediaPlayer.NoMedia].indexOf(player.status) == -1
gradient: Gradient {
GradientStop { position: 0.0; color: Theme.rgba(palette.overlayBackgroundColor, 0.15); }
GradientStop { position: 1.0; color: Theme.rgba(palette.overlayBackgroundColor, 0.30); }
}
Item {
id: progress
height: progressSlider.height + 2 * Theme.paddingMedium
width: parent.width
Label {
id: playedTime
anchors.left: parent.left
anchors.leftMargin: Theme.horizontalPageMargin
anchors.verticalCenter: progressSlider.verticalCenter
text: timeToText(player.position)
}
Slider {
id: progressSlider
enabled: player.seekable
value: player.position
maximumValue: player.duration
stepSize: 1000
anchors.left: playedTime.right
anchors.right: totalTime.left
anchors.verticalCenter: parent.verticalCenter
onDownChanged: if (!down) { player.seek(value) }
}
Label {
id: totalTime
anchors.right: parent.right
anchors.rightMargin: Theme.horizontalPageMargin
anchors.verticalCenter: progress.verticalCenter
text: timeToText(player.duration)
}
}
}
function timeToText(time) {
if (time < 0) return "??:??:??"
var hours = Math.floor(time / (60 * 60 * 1000))
var left = time % (60 * 60 * 1000)
var minutes = Math.floor(left / (60 * 1000))
left = time % (60 * 1000)
var seconds = Math.floor(left / 1000)
return hours + ":" + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "")+ seconds
}
Connections {
target: player
onStatusChanged: {
console.log("New mediaPlayer status: " + player.status)
switch(player.status) {
case MediaPlayer.Loaded:
case MediaPlayer.Buffering:
show(false)
break;
case MediaPlayer.Buffered:
hide(false)
break;
}
}
}
function show(manual) {
if (manual) {
_manuallyActivated = true
inactivityTimer.restart()
} else {
_manuallyActivated = false
}
opacity = 1
}
function hide(manual) {
// Don't hide if the user decided on their own to show the hud
if (!manual && _manuallyActivated) return;
opacity = 0
}
Timer {
id: inactivityTimer
interval: 5000
onTriggered: {
hide(true)
}
}
}

18
qml/cover/PosterCover.qml Normal file
View file

@ -0,0 +1,18 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
import nl.netsoj.chris.Jellyfin 1.0
import "../components"
CoverBackground {
property var mData: appWindow.itemData
RemoteImage {
anchors.fill: parent
source: mData.ImageTags["Primary"] ? ApiClient.baseUrl + "/Items/" + mData.Id
+ "/Images/Primary?maxHeight=" + height + "&tag=" + mData.ImageTags["Primary"]
: ""
fillMode: Image.PreserveAspectCrop
}
}

28
qml/cover/VideoCover.qml Normal file
View file

@ -0,0 +1,28 @@
import QtQuick 2.6
import QtMultimedia 5.6
import Sailfish.Silica 1.0
CoverBackground {
readonly property MediaPlayer player: appWindow.mediaPlayer
Rectangle {
anchors.fill: parent
color: "black"
/*VideoOutput {
id: coverOutput
anchors.fill: parent
source: player
}*/
}
CoverActionList {
CoverAction {
id: playPause
iconSource: player.playbackState === MediaPlayer.PlayingState ? "image://theme/icon-cover-pause"
: "image://theme/icon-cover-play"
}
}
}

View file

@ -1,5 +1,6 @@
import QtQuick 2.0
import Sailfish.Silica 1.0
import QtMultimedia 5.6
import nl.netsoj.chris.Jellyfin 1.0
import Nemo.Notifications 1.0
@ -10,6 +11,8 @@ ApplicationWindow {
id: appWindow
property bool isInSetup: false
property bool _hasInitialized: false
readonly property MediaPlayer mediaPlayer: _mediaPlayer
property var itemData
//property alias backdrop: backdrop
Connections {
@ -19,6 +22,11 @@ ApplicationWindow {
//onConnectionSuccess: errorNotification.show("Success: " + loginMessage)
}
MediaPlayer {
id: _mediaPlayer
autoPlay: true
}
/*GlassyBackground {
id: backdrop
anchors.fill: parent
@ -37,19 +45,29 @@ ApplicationWindow {
onSetupRequired: {
if (!isInSetup) {
isInSetup = true;
pageStack.replace(Qt.resolvedUrl("pages/AddServerPage.qml"), {"backNavigation": false});
pageStack.replace(Qt.resolvedUrl("pages/setup/AddServerPage.qml"), {"backNavigation": false});
}
}
}
onStatusChanged: {
if (status == PageStatus.Active && !_hasInitialized) {
_hasInitialized = true;
ApiClient.initialize();
ApiClient.restoreSavedSession();
}
}
}
}
cover: Qt.resolvedUrl("cover/CoverPage.qml")
cover: {
if ([MediaPlayer.NoMedia, MediaPlayer.InvalidMedia, MediaPlayer.UnknownStatus].indexOf(mediaPlayer.status) >= 0) {
if (itemData) {
return Qt.resolvedUrl("cover/PosterCover.qml")
} else {
return Qt.resolvedUrl("cover/CoverPage.qml")
}
} else if (mediaPlayer.hasVideo){
return Qt.resolvedUrl("cover/VideoCover.qml")
}
}
allowedOrientations: Orientation.All
Notification {

View file

@ -4,6 +4,7 @@ import Sailfish.Silica 1.0
import nl.netsoj.chris.Jellyfin 1.0
import "../components"
import "../compontents/details"
Page {
id: pageRoot
@ -102,6 +103,7 @@ Page {
IconButton {
id: playButton
icon.source: "image://theme/icon-l-play"
onPressed: pageStack.push(Qt.resolvedUrl("VideoPage.qml"), {"itemId": itemId, "itemData": itemData})
}
}
}
@ -122,6 +124,7 @@ Page {
onStatusChanged: {
if (status == PageStatus.Deactivating) {
backdrop.clear()
appWindow.itemData = ({})
}
}
@ -129,9 +132,10 @@ Page {
target: ApiClient
onItemFetched: {
if (itemId === pageRoot.itemId) {
console.log(JSON.stringify(result))
//console.log(JSON.stringify(result))
pageRoot.itemData = result
pageRoot._loading = false
appWindow.itemData = result
}
}
}

View file

@ -5,6 +5,7 @@ import nl.netsoj.chris.Jellyfin 1.0
import "../components"
// Test
Page {
id: page
@ -90,11 +91,13 @@ Page {
delegate: LibraryItemDelegate {
property string id: model.id
title: model.name
poster: ApiClient.baseUrl + "/Items/" + model.id + "/Images/Primary?maxHeight=" + height + "&tag=" + model.imageTags["Primary"]
poster: model.imageTags["Primary"] ? ApiClient.baseUrl + "/Items/" + model.id
+ "/Images/Primary?maxHeight=" + height + "&tag=" + model.imageTags["Primary"]
: ""
landscape: true
onClicked: {
pageStack.push(Qt.resolvedUrl("DetailBasePage.qml"), {"itemId": model.id})
pageStack.push(Qt.resolvedUrl("DetailPage.qml"), {"itemId": model.id})
}
}
HorizontalScrollDecorator {}

37
qml/pages/VideoPage.qml Normal file
View file

@ -0,0 +1,37 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
import "../components"
Page {
id: videoPage
property string itemId
property var itemData
allowedOrientations: Orientation.All
palette.colorScheme: Theme.LightOnDark
showNavigationIndicator: videoPlayer.hudVisible
Rectangle {
anchors.fill: parent
color: "black"
}
VideoPlayer {
id: videoPlayer
anchors.fill: parent
itemId: videoPage.itemId
onLandscapeChanged: {
console.log("Is landscape: " + landscape)
//appWindow.orientation = landscape ? Orientation.Landscape : Orientation.Portrait
videoPage.allowedOrientations = landscape ? Orientation.LandscapeMask : Orientation.PortraitMask
}
player: appWindow.mediaPlayer
title: itemData.Name
}
onStatusChanged: {
if (status == PageStatus.Inactive) {
videoPlayer.stop()
}
}
}

View file

@ -2,7 +2,7 @@ import QtQuick 2.6
import Sailfish.Silica 1.0
import nl.netsoj.chris.Jellyfin 1.0
import "../components"
import "../../components"
Dialog {
property string loginMessage
@ -27,7 +27,7 @@ Dialog {
onAuthenticatedChanged: {
if (ApiClient.authenticated) {
console.log("authenticated!")
pageStack.replaceAbove(pageStack.previousPage(firstPage), Qt.resolvedUrl("MainPage.qml"))
pageStack.replaceAbove(pageStack.previousPage(firstPage), Qt.resolvedUrl("../MainPage.qml"))
}
}
onAuthenticationError: {

0
qml/pages/setup/a Normal file
View file