More work on Multimedia
This commit is contained in:
parent
8ad40dcf56
commit
2ad624329c
@ -185,5 +185,8 @@
|
||||
<file>ui/connection/SetupWizard.qml</file>
|
||||
<file>ui/system/NetworkSettingsPage.qml</file>
|
||||
<file>ui/devicepages/DeviceBrowserPage.qml</file>
|
||||
<file>ui/devicelistpages/MediaDeviceListPage.qml</file>
|
||||
<file>ui/components/MediaControls.qml</file>
|
||||
<file>ui/components/MediaArtworkImage.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -30,6 +30,8 @@ Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
z: -1
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
// horizontalAlignment: Image.AlignTop
|
||||
// opacity: .5
|
||||
// Rectangle {
|
||||
// anchors.fill: parent
|
||||
|
||||
47
nymea-app/ui/components/MediaArtworkImage.qml
Normal file
47
nymea-app/ui/components/MediaArtworkImage.qml
Normal file
@ -0,0 +1,47 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property Device device: null
|
||||
|
||||
readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null
|
||||
readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null
|
||||
|
||||
readonly property StateType playerTypeStateType: device ? device.deviceClass.stateTypes.findByName("playerType") : null
|
||||
readonly property State playerTypeState: playerTypeStateType ? device.states.getState(playerTypeStateType.id) : null
|
||||
|
||||
Pane {
|
||||
Material.elevation: 2
|
||||
anchors.centerIn: parent
|
||||
height: fallback.visible ? Math.min(parent.height, parent.width) : artworkImage.paintedHeight - 1
|
||||
width: fallback.visible ? Math.min(parent.height, parent.width) : artworkImage.paintedWidth - 1
|
||||
padding: 0
|
||||
contentItem: Rectangle {
|
||||
color: "black"
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: artworkImage
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: root.artworkState.value
|
||||
visible: source !== ""
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
id: fallback
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(parent.height, parent.width) - app.margins * 2
|
||||
height: Math.min(parent.height, parent.width) - app.margins * 2
|
||||
|
||||
name: root.playerTypeState.value === "video" ? "../images/stock_video.svg" : "../images/stock_music.svg"
|
||||
visible: artworkImage.status !== Image.Ready || artworkImage.source === ""
|
||||
// color: app.primaryColor
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
73
nymea-app/ui/components/MediaControls.qml
Normal file
73
nymea-app/ui/components/MediaControls.qml
Normal file
@ -0,0 +1,73 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
implicitHeight: iconSize + app.margins
|
||||
|
||||
property Device device: null
|
||||
property int iconSize: app.iconSize * 1.5
|
||||
|
||||
readonly property StateType playbackStateType: device ? device.deviceClass.stateTypes.findByName("playbackStatus") : null
|
||||
readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null
|
||||
|
||||
function executeAction(actionName, params) {
|
||||
var actionTypeId = device.deviceClass.actionTypes.findByName(actionName).id;
|
||||
engine.deviceManager.executeAction(device.id, actionTypeId, params)
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
ProgressButton {
|
||||
Layout.preferredHeight: root.iconSize * .6
|
||||
Layout.preferredWidth: height
|
||||
imageSource: "../images/media-skip-backward.svg"
|
||||
longpressImageSource: "../images/media-seek-backward.svg"
|
||||
enabled: root.playbackState.value !== "Stopped"
|
||||
repeat: true
|
||||
onClicked: {
|
||||
root.executeAction("skipBack")
|
||||
}
|
||||
onLongpressed: {
|
||||
root.executeAction("fastRewind")
|
||||
}
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
ProgressButton {
|
||||
Layout.preferredHeight: root,iconSize
|
||||
Layout.preferredWidth: height
|
||||
imageSource: root.playbackState && root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg"
|
||||
longpressImageSource: "../images/media-playback-stop.svg"
|
||||
longpressEnabled: root.playbackState.value !== "Stopped"
|
||||
|
||||
onClicked: {
|
||||
if (root.playbackState.value === "Playing") {
|
||||
root.executeAction("pause")
|
||||
} else {
|
||||
root.executeAction("play")
|
||||
}
|
||||
}
|
||||
|
||||
onLongpressed: {
|
||||
root.executeAction("stop")
|
||||
}
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
ProgressButton {
|
||||
Layout.preferredHeight: root.iconSize * .6
|
||||
Layout.preferredWidth: height
|
||||
imageSource: "../images/media-skip-forward.svg"
|
||||
longpressImageSource: "../images/media-seek-forward.svg"
|
||||
enabled: root.playbackState.value !== "Stopped"
|
||||
repeat: true
|
||||
onClicked: {
|
||||
root.executeAction("skipNext")
|
||||
}
|
||||
onLongpressed: {
|
||||
root.executeAction("fastForward")
|
||||
}
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
170
nymea-app/ui/devicelistpages/MediaDeviceListPage.qml
Normal file
170
nymea-app/ui/devicelistpages/MediaDeviceListPage.qml
Normal file
@ -0,0 +1,170 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls.Material 2.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import Nymea 1.0
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../components"
|
||||
|
||||
DeviceListPageBase {
|
||||
id: root
|
||||
|
||||
header: NymeaHeader {
|
||||
text: qsTr("Media")
|
||||
onBackPressed: pageStack.pop()
|
||||
}
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
model: root.devicesProxy
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: itemDelegate
|
||||
width: parent.width
|
||||
|
||||
property bool inline: width > 500
|
||||
|
||||
property Device device: devicesProxy.get(index);
|
||||
property DeviceClass deviceClass: device.deviceClass
|
||||
|
||||
readonly property StateType playbackStateType: deviceClass.stateTypes.findByName("playbackStatus")
|
||||
readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null
|
||||
|
||||
bottomPadding: index === ListView.view.count - 1 ? topPadding : 0
|
||||
contentItem: Pane {
|
||||
id: contentItem
|
||||
Material.elevation: 2
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
contentItem: ItemDelegate {
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: app.mediumFont + app.margins
|
||||
color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05)
|
||||
RowLayout {
|
||||
anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right; margins: app.margins }
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: model.name
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: app.iconSize * .5
|
||||
Layout.preferredWidth: height
|
||||
name: "../images/battery/battery-020.svg"
|
||||
visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true
|
||||
}
|
||||
ColorIcon {
|
||||
Layout.preferredHeight: app.iconSize * .5
|
||||
Layout.preferredWidth: height
|
||||
name: "../images/dialog-warning-symbolic.svg"
|
||||
visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false
|
||||
color: "red"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
id: leftColummn
|
||||
Layout.margins: app.margins
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: itemDelegate.playbackState.value === "Stopped" ?
|
||||
qsTr("No playback")
|
||||
: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("title").id).value
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
// font.pixelSize: app.largeFont
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("artist").id).value
|
||||
font.pixelSize: app.smallFont
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("collection").id).value
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: app.smallFont
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
MediaControls {
|
||||
visible: itemDelegate.deviceClass.interfaces.indexOf("mediacontroller") >= 0
|
||||
device: itemDelegate.device
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.preferredHeight: leftColummn.height + app.margins * 2
|
||||
Layout.preferredWidth: height * .7
|
||||
|
||||
Item {
|
||||
id: artworkContainer
|
||||
anchors.fill: parent
|
||||
Image {
|
||||
id: artworkImage
|
||||
width: artworkImage.sourceSize.width * height / artworkImage.sourceSize.height
|
||||
anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
|
||||
readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null
|
||||
readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null
|
||||
source: artworkState ? artworkState.value : ""
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: maskRect
|
||||
anchors.centerIn: parent
|
||||
height: parent.width
|
||||
width: parent.height
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0; color: "transparent" }
|
||||
GradientStop { position: 1; color: "red" }
|
||||
}
|
||||
}
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
property variant source: ShaderEffectSource {
|
||||
sourceItem: artworkContainer
|
||||
hideSource: true
|
||||
}
|
||||
property variant mask: ShaderEffectSource {
|
||||
sourceItem: maskRect
|
||||
hideSource: true
|
||||
}
|
||||
|
||||
fragmentShader: "
|
||||
varying highp vec2 qt_TexCoord0;
|
||||
uniform sampler2D source;
|
||||
uniform sampler2D mask;
|
||||
void main(void)
|
||||
{
|
||||
highp vec4 sourceColor = texture2D(source, qt_TexCoord0);
|
||||
highp float alpha = texture2D(mask, vec2(qt_TexCoord0.y, qt_TexCoord0.x)).a;
|
||||
sourceColor *= alpha;
|
||||
gl_FragColor = sourceColor;
|
||||
}
|
||||
"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
enterPage(index, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -41,6 +41,7 @@ DevicePageBase {
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
anchors.fill: parent
|
||||
interactive: root.deviceClass.browsable
|
||||
|
||||
Item {
|
||||
GridLayout {
|
||||
@ -51,32 +52,10 @@ DevicePageBase {
|
||||
columnSpacing: app.margins
|
||||
rowSpacing: app.margins
|
||||
|
||||
Pane {
|
||||
Layout.fillWidth: true
|
||||
MediaArtworkImage {
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumWidth: parent.width / 2
|
||||
Material.elevation: 2
|
||||
padding: 0
|
||||
|
||||
contentItem: Rectangle {
|
||||
color: app.foregroundColor
|
||||
|
||||
Image {
|
||||
id: artworkImage
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: root.stateValue("artwork")
|
||||
}
|
||||
|
||||
ColorIcon {
|
||||
id: fallback
|
||||
anchors.fill: parent
|
||||
anchors.margins: app.margins * 2
|
||||
name: root.stateValue("playerType") === "video" ? "../images/stock_video.svg" : "../images/stock_music.svg"
|
||||
visible: artworkImage.status !== Image.Ready || artworkImage.source === ""
|
||||
color: app.primaryColor
|
||||
}
|
||||
}
|
||||
Layout.preferredWidth: parent.width / parent.columns
|
||||
device: root.device
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@ -110,62 +89,9 @@ DevicePageBase {
|
||||
text: root.stateValue("collection")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
ProgressButton {
|
||||
Layout.preferredHeight: app.iconSize
|
||||
Layout.preferredWidth: height
|
||||
imageSource: "../images/media-skip-backward.svg"
|
||||
longpressImageSource: "../images/media-seek-backward.svg"
|
||||
repeat: true
|
||||
|
||||
onClicked: {
|
||||
root.executeAction("skipBack")
|
||||
}
|
||||
onLongpressed: {
|
||||
root.executeAction("fastRewind")
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
ProgressButton {
|
||||
Layout.preferredHeight: app.iconSize * 2
|
||||
Layout.preferredWidth: height
|
||||
imageSource: root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg"
|
||||
longpressImageSource: "../images/media-playback-stop.svg"
|
||||
longpressEnabled: root.playbackState.value !== "Stopped"
|
||||
|
||||
onClicked: {
|
||||
if (root.playbackState.value === "Playing") {
|
||||
root.executeAction("pause")
|
||||
} else {
|
||||
root.executeAction("play")
|
||||
}
|
||||
}
|
||||
|
||||
onLongpressed: {
|
||||
root.executeAction("stop")
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
ProgressButton {
|
||||
Layout.preferredHeight: app.iconSize
|
||||
Layout.preferredWidth: height
|
||||
imageSource: "../images/media-skip-forward.svg"
|
||||
longpressImageSource: "../images/media-seek-forward.svg"
|
||||
repeat: true
|
||||
onClicked: {
|
||||
root.executeAction("skipNext")
|
||||
}
|
||||
onLongpressed: {
|
||||
root.executeAction("fastForward")
|
||||
}
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
MediaControls {
|
||||
device: root.device
|
||||
iconSize: app.iconSize * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -279,6 +205,7 @@ DevicePageBase {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 2
|
||||
visible: root.deviceClass.browsable
|
||||
Rectangle {
|
||||
height: parent.height
|
||||
width: parent.width / 2
|
||||
|
||||
@ -13,13 +13,7 @@ MainPageTile {
|
||||
disconnected: devicesSubProxyConnectables.count > 0
|
||||
batteryCritical: devicesSubProxyBattery.count > 0
|
||||
|
||||
backgroundImage: currentDevice.deviceClass.interfaces.indexOf("mediametadataprovider") >= 0 ?
|
||||
currentDevice.states.getState(currentDevice.deviceClass.stateTypes.findByName("artwork").id).value : ""
|
||||
|
||||
property int currentDeviceIndex: 0
|
||||
readonly property Device currentDevice: devicesProxy.get(currentDeviceIndex)
|
||||
// readonly property State currentBackgroundState: currentDevice.state
|
||||
|
||||
backgroundImage: inlineControlLoader.item && inlineControlLoader.item.hasOwnProperty("backgroundImage") ? inlineControlLoader.item.backgroundImage : ""
|
||||
|
||||
onClicked: {
|
||||
var page;
|
||||
@ -55,6 +49,9 @@ MainPageTile {
|
||||
case "powersocket":
|
||||
page = "PowerSocketsDeviceListPage.qml";
|
||||
break;
|
||||
case "media":
|
||||
page = "MediaDeviceListPage.qml";
|
||||
break;
|
||||
default:
|
||||
page = "GenericDeviceListPage.qml"
|
||||
}
|
||||
@ -84,6 +81,9 @@ MainPageTile {
|
||||
filterBatteryCritical: true
|
||||
}
|
||||
|
||||
property int currentDeviceIndex: 0
|
||||
readonly property Device currentDevice: devicesProxy.get(currentDeviceIndex)
|
||||
|
||||
contentItem: Loader {
|
||||
id: inlineControlLoader
|
||||
anchors {
|
||||
@ -105,7 +105,6 @@ MainPageTile {
|
||||
// return labelComponent;
|
||||
|
||||
case "light":
|
||||
case "media":
|
||||
case "garagegate":
|
||||
case "blind":
|
||||
case "extendedblind":
|
||||
@ -115,12 +114,66 @@ MainPageTile {
|
||||
case "extendedawning":
|
||||
case "powersocket":
|
||||
return buttonComponent
|
||||
case "media":
|
||||
return mediaControlComponent
|
||||
default:
|
||||
console.warn("DevicesPageDelegate, inlineControl: Unhandled interface", model.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: mediaControlComponent
|
||||
RowLayout {
|
||||
id: inlineMediaControl
|
||||
|
||||
property string backgroundImage: artworkState ? artworkState.value : ""
|
||||
|
||||
property int currentDeviceIndex: 0
|
||||
readonly property Device currentDevice: devicesProxy.get(currentDeviceIndex)
|
||||
readonly property StateType playbackStateType: currentDevice.deviceClass.stateTypes.findByName("playbackStatus")
|
||||
readonly property State playbackState: currentDevice.states.getState(playbackStateType.id)
|
||||
readonly property StateType artworkStateType: currentDevice.deviceClass.stateTypes.findByName("artwork")
|
||||
readonly property State artworkState: artworkStateType ? currentDevice.states.getState(artworkStateType.id) : null
|
||||
|
||||
Component.onCompleted: {
|
||||
for (var i = 0; i < devicesProxy.count; i++) {
|
||||
var d = devicesProxy.get(i);
|
||||
var st = d.deviceClass.stateTypes.findByName("playbackStatus")
|
||||
var s = d.states.getState(st.id)
|
||||
s.valueChanged.connect(function() {updateTile()})
|
||||
}
|
||||
updateTile();
|
||||
}
|
||||
|
||||
function updateTile() {
|
||||
var playingIndex = -1;
|
||||
var pausedIndex = -1;
|
||||
for (var i = 0; i < devicesProxy.count; i++) {
|
||||
var d = devicesProxy.get(i);
|
||||
var st = d.deviceClass.stateTypes.findByName("playbackStatus");
|
||||
if (!st) continue;
|
||||
var s = d.states.getState(st.id);
|
||||
if (playingIndex === -1 && s.value === "Playing") {
|
||||
playingIndex = i;
|
||||
} else if (pausedIndex === -1 && s.value === "Paused") {
|
||||
pausedIndex = -i;
|
||||
}
|
||||
}
|
||||
if (playingIndex !== -1) {
|
||||
currentDeviceIndex = playingIndex;
|
||||
} else if (pausedIndex !== -1) {
|
||||
currentDeviceIndex = pausedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
MediaControls {
|
||||
iconSize: app.iconSize * 1.2
|
||||
device: inlineMediaControl.currentDevice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: buttonComponent
|
||||
ColumnLayout {
|
||||
|
||||
Reference in New Issue
Block a user