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:
parent
53b3eac213
commit
92a18c4fa5
28 changed files with 889 additions and 51 deletions
|
@ -71,6 +71,6 @@ Item {
|
|||
right: parent.right
|
||||
}
|
||||
width: parent.width
|
||||
height: children[0].height
|
||||
height: children.length > 0 ? children[0].height : 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
64
qml/components/VideoPlayer.qml
Normal file
64
qml/components/VideoPlayer.qml
Normal 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 = ""
|
||||
}
|
||||
}
|
5
qml/components/itemdetails/FilmDetails.qml
Normal file
5
qml/components/itemdetails/FilmDetails.qml
Normal file
|
@ -0,0 +1,5 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
Item {
|
||||
|
||||
}
|
5
qml/components/itemdetails/SeriesDetails.qml
Normal file
5
qml/components/itemdetails/SeriesDetails.qml
Normal file
|
@ -0,0 +1,5 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
Item {
|
||||
|
||||
}
|
166
qml/components/videoplayer/VideoHud.qml
Normal file
166
qml/components/videoplayer/VideoHud.qml
Normal 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
18
qml/cover/PosterCover.qml
Normal 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
28
qml/cover/VideoCover.qml
Normal 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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
37
qml/pages/VideoPage.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
0
qml/pages/setup/a
Normal file
Loading…
Add table
Add a link
Reference in a new issue