binding: mediaplayer: switch to mediaplayer backend binding 03/11603/1
authorMatt Ranostay <matt.ranostay@konsulko.com>
Mon, 16 Oct 2017 21:58:28 +0000 (14:58 -0700)
committerMatt Ranostay <matt.ranostay@konsulko.com>
Sun, 29 Oct 2017 18:24:52 +0000 (11:24 -0700)
Stop using the QTMultimedia plugin and switch to the
agl-service-mediaplayer binding for media playback.

Bug-AGL: SPEC-999
Change-Id: I5b380dc3cc908ed7bb2aa610c819f9f76f442829
Signed-off-by: Matt Ranostay <matt.ranostay@konsulko.com>
app/MediaPlayer.qml
app/api/LightMediaScanner.qml [deleted file]
app/api/MediaPlayer.qml [new file with mode: 0644]
app/mediaplayer.qrc
package/config.xml

index 77538a6..bcf09cf 100644 (file)
@@ -25,8 +25,8 @@ import 'api' as API
 ApplicationWindow {
     id: root
 
-    API.LightMediaScanner {
-        id: binding
+    API.MediaPlayer {
+        id: player
         url: bindingAddress
     }
 
@@ -35,17 +35,6 @@ ApplicationWindow {
         url: bindingAddress
     }
 
-    MediaPlayer {
-        id: player
-        audioRole: MediaPlayer.MusicRole
-        autoLoad: true
-        playlist: playlist
-
-        function time2str(value) {
-            return Qt.formatTime(new Date(value), 'mm:ss')
-        }
-    }
-
     Timer {
         id: timer
         interval: 250
@@ -57,12 +46,10 @@ ApplicationWindow {
         }
     }
 
-    Playlist {
+    ListModel {
         id: playlist
-        playbackMode: random.checked ? Playlist.Random : loop.checked ? Playlist.Loop : Playlist.Sequential
     }
 
-
     ColumnLayout {
         anchors.fill: parent
         Item {
@@ -77,7 +64,7 @@ ApplicationWindow {
                 anchors.bottom: parent.bottom
                 height: sourceSize.height * width / sourceSize.width
                 fillMode: Image.PreserveAspectCrop
-                source: player.metaData.coverArtImage ? player.metaData.coverArtImage : ''
+                source: player.cover_art ? player.cover_art : ''
                 visible: bluetooth.av_connected == false
             }
 
@@ -109,8 +96,10 @@ ApplicationWindow {
                             ToggleButton {
                                 id: loop
                                 visible: bluetooth.connected == false
+                                checked: player.loop_state
                                 offImage: './images/AGL_MediaPlayer_Loop_Inactive.svg'
                                 onImage: './images/AGL_MediaPlayer_Loop_Active.svg'
+                                onClicked: { player.loop(checked) }
                             }
                         }
                         ColumnLayout {
@@ -118,13 +107,13 @@ ApplicationWindow {
                             Label {
                                 id: title
                                 Layout.alignment: Layout.Center
-                                text: bluetooth.av_connected ? bluetooth.title : (player.metaData.title ? player.metaData.title : '')
+                                text: bluetooth.av_connected ? bluetooth.title : (player.title ? player.title : '')
                                 horizontalAlignment: Label.AlignHCenter
                                 verticalAlignment: Label.AlignVCenter
                             }
                             Label {
                                 Layout.alignment: Layout.Center
-                                text: bluetooth.av_connected ? bluetooth.artist : (player.metaData.contributingArtist ? player.metaData.contributingArtist : '')
+                                text: bluetooth.av_connected ? bluetooth.artist : (player.artist ? player.artist : '')
                                 horizontalAlignment: Label.AlignHCenter
                                 verticalAlignment: Label.AlignVCenter
                                 font.pixelSize: title.font.pixelSize * 0.6
@@ -176,7 +165,7 @@ ApplicationWindow {
                                     bluetooth.sendMediaCommand("Previous")
                                     bluetooth.position = 0
                                 } else {
-                                    playlist.previous()
+                                    player.previous()
                                 }
                             }
                         }
@@ -192,7 +181,7 @@ ApplicationWindow {
                             }
                             states: [
                                 State {
-                                    when: player.playbackState === MediaPlayer.PlayingState
+                                    when: player.running === true
                                     PropertyChanges {
                                         target: play
                                         offImage: './images/AGL_MediaPlayer_Player_Pause.svg'
@@ -217,7 +206,7 @@ ApplicationWindow {
                                 if (bluetooth.av_connected) {
                                     bluetooth.sendMediaCommand("Next")
                                 } else {
-                                    playlist.next()
+                                    player.next()
                                 }
                             }
                         }
@@ -247,11 +236,6 @@ ApplicationWindow {
             Layout.fillHeight: true
             Layout.preferredHeight: 407
 
-           PlaylistWithMetadata {
-               id: playlistmodel
-                source: playlist
-            }
-
             ListView {
                 anchors.fill: parent
                 id: playlistview
@@ -262,8 +246,8 @@ ApplicationWindow {
                     text: 'PLAYLIST'
                     opacity: 0.5
                 }
-                model: playlistmodel
-                currentIndex: playlist.currentIndex
+                model: playlist
+                currentIndex: -1
 
                 delegate: MouseArea {
                     id: delegate
@@ -273,12 +257,6 @@ ApplicationWindow {
                         anchors.fill: parent
                         anchors.leftMargin: 50
                         anchors.rightMargin: 50
-                        Image {
-                            source: model.coverArt
-                            fillMode: Image.PreserveAspectFit
-                            Layout.preferredWidth: delegate.height
-                            Layout.preferredHeight: delegate.height
-                        }
                         ColumnLayout {
                             Layout.fillWidth: true
                             Label {
@@ -292,14 +270,14 @@ ApplicationWindow {
                                 font.pixelSize: 32
                             }
                         }
-                        Label {
-                            text: player.time2str(model.duration)
-                            color: '#66FF99'
-                            font.pixelSize: 32
-                        }
+                        //Label {
+                        //    text: player.time2str(model.duration)
+                        //    color: '#66FF99'
+                        //    font.pixelSize: 32
+                        //}
                     }
                     onClicked: {
-                        playlist.currentIndex = model.index
+                        player.pick_track(playlistview.model.get(index).index)
                         player.play()
                     }
                 }
diff --git a/app/api/LightMediaScanner.qml b/app/api/LightMediaScanner.qml
deleted file mode 100644 (file)
index b61ff0d..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2016 The Qt Company Ltd.
- * Copyright (C) 2017 Konsulko Group
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import QtQuick 2.6
-import QtWebSockets 1.0
-
-WebSocket {
-    id: root
-    active: true
-    url: bindingAddress
-
-    property string statusString: "waiting..."
-    property string apiString: "mediascanner"
-    property var verbs: []
-    property var items: []
-    property string payloadLength: "9999"
-
-    readonly property var msgid: {
-        "call": 2,
-        "retok": 3,
-        "reterr": 4,
-        "event": 5
-    }
-
-    function validateItem(media) {
-        for (var i = 0; i < media.length; i++) {
-            var item = media[i].path
-            if (root.items.indexOf(item) < 0) {
-                playlist.addItem(item)
-                root.items.push(item)
-            }
-        }
-    }
-
-    onTextMessageReceived: {
-        var json = JSON.parse(message)
-        console.debug("Raw response: " + message)
-        var request = json[2].request
-        var response = json[2].response
-        console.debug("response: " + JSON.stringify(response))
-        switch (json[0]) {
-            case msgid.call:
-                break
-            case msgid.retok:
-                root.statusString = request.status
-                var verb = verbs.shift()
-                if (verb == "media_result") {
-                    console.debug("Media result returned")
-                    validateItem(response.Media)
-                }
-                break
-            case msgid.reterr:
-                root.statusString = "Bad return value, binding probably not installed"
-                break
-            case msgid.event:
-                var payload = JSON.parse(JSON.stringify(json[2]))
-                var event = payload.event
-                if (event == "mediascanner/media_added") {
-                    console.debug("Media is inserted")
-                    validateItem(json[2].data.Media)
-                } else if (event == "mediascanner/media_removed") {
-                    var removed = 0
-                    console.debug("Media is removed")
-                    player.stop()
-
-                    for (var i = 0; i < root.items.length; i++) {
-                        if (root.items[i].startsWith(json[2].data.Path)) {
-                            playlist.removeItem(i - removed++)
-                        }
-                    }
-                    root.items = root.items.filter(function (item) { return !item.startsWith(json[2].data.Path) })
-                }
-                break
-        }
-    }
-
-    onStatusChanged: {
-        switch (status) {
-            case WebSocket.Open:
-            console.debug("onStatusChanged: Open")
-            sendSocketMessage("subscribe", { value: "media_added" })
-            sendSocketMessage("subscribe", { value: "media_removed" })
-            root.populateMediaPlaylist()
-            break
-            case WebSocket.Error:
-            root.statusString = "WebSocket error: " + root.errorString
-            break
-        }
-    }
-
-    function sendSocketMessage(verb, parameter) {
-        var requestJson = [ msgid.call, payloadLength, apiString + '/'
-        + verb, parameter ]
-        console.debug("sendSocketMessage: " + JSON.stringify(requestJson))
-        verbs.push(verb)
-        sendTextMessage(JSON.stringify(requestJson))
-    }
-
-    function populateMediaPlaylist() {
-        sendSocketMessage("media_result", 'None')
-    }
-}
diff --git a/app/api/MediaPlayer.qml b/app/api/MediaPlayer.qml
new file mode 100644 (file)
index 0000000..8b38c3f
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (C) 2017 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import QtQuick 2.6
+import QtWebSockets 1.0
+
+WebSocket {
+    id: root
+    active: true
+    url: bindingAddress
+
+    property string statusString: "waiting..."
+    property string apiString: "mediaplayer"
+    property var verbs: []
+    property string payloadLength: "9999"
+    property string cover_art: ""
+    property string title: ""
+    property string artist: ""
+    property int duration: 0
+    property int position: 0
+    property int old_index: -1
+
+    property bool loop_state: false
+    property bool running: false
+
+    readonly property var msgid: {
+        "call": 2,
+        "retok": 3,
+        "reterr": 4,
+        "event": 5
+    }
+
+    onTextMessageReceived: {
+        var json = JSON.parse(message)
+        console.debug("Raw response: " + message)
+        var request = json[2].request
+        var response = json[2].response
+        console.debug("response: " + JSON.stringify(response))
+        switch (json[0]) {
+            case msgid.call:
+                break
+            case msgid.retok:
+                root.statusString = request.status
+                var verb = verbs.shift()
+                if (verb == "playlist") {
+                    console.debug("Media result returned")
+                    var media = response.list
+
+                    for (var i = 0; i < media.length; i++) {
+                        var item = media[i]
+                        playlist.append({ "index": item.index, "artist": item.artist ? item.artist : '', "title": item.title ? item.title : '' })
+                        if (item.selected) {
+                            playlistview.currentIndex = i
+                        }
+                    }
+                } else if (verb == "controls") {
+                    if (response) {
+                        root.running = response.playing
+                    }
+                } else if (verb == "metadata") {
+                    root.cover_art = response.image ? response.image : ''
+                }
+                break
+            case msgid.reterr:
+                root.statusString = "Bad return value, binding probably not installed"
+                var verb = verbs.shift()
+                break
+            case msgid.event:
+                var payload = JSON.parse(JSON.stringify(json[2]))
+                var event = payload.event
+                if (event == "mediaplayer/playlist") {
+                    console.debug("Media playlist is updated")
+                    var media = json[2].data.list
+
+                    if (!root.running) {
+                        root.clearPlaylist()
+                    }
+
+                    playlist.clear()
+
+                    for (var i = 0; i < media.length; i++) {
+                        var item = media[i]
+
+                        playlist.append({ "index": item.index, "artist": item.artist ? item.artist : '', "title": item.title ? item.title : '' })
+                        if (item.selected) {
+                            playlistview.currentIndex = i
+                        }
+                    }
+
+                } else if (event == "mediaplayer/metadata") {
+                    var data = json[2].data
+
+                    if (data.status == "stopped") {
+                        root.running = false
+                        root.clearPlaylist()
+                        break
+                    }
+
+                    root.running = true
+                    root.position = data.position
+                    root.duration = data.duration
+
+                    playlistview.currentIndex = data.index
+
+                    if (playlistview.currentIndex != root.old_index) {
+                        sendSocketMessage("metadata", 'None')
+                        root.old_index = data.index
+                    }
+
+                    root.title = data.title ? data.title : ''
+                    root.artist = data.artist ? data.artist : ''
+                }
+                break
+        }
+    }
+
+    onStatusChanged: {
+        switch (status) {
+            case WebSocket.Open:
+            console.debug("onStatusChanged: Open")
+            sendSocketMessage("subscribe", { value: "metadata" })
+            sendSocketMessage("playlist", 'None')
+            sendSocketMessage("subscribe", { value: "playlist" })
+            break
+            case WebSocket.Error:
+            root.statusString = "WebSocket error: " + root.errorString
+            break
+        }
+    }
+
+    function sendSocketMessage(verb, parameter) {
+        var requestJson = [ msgid.call, payloadLength, apiString + '/'
+        + verb, parameter ]
+        console.debug("sendSocketMessage: " + JSON.stringify(requestJson))
+        verbs.push(verb)
+        sendTextMessage(JSON.stringify(requestJson))
+    }
+
+    function loop(value) {
+        sendSocketMessage("controls", { "value": "loop", "state": value })
+        root.loop_state = value
+    }
+
+    function next() {
+        sendSocketMessage("controls", { "value": "next" })
+    }
+
+    function previous() {
+        sendSocketMessage("controls", { "value": "previous" })
+    }
+
+    function play() {
+        sendSocketMessage("controls", { "value": "play" })
+    }
+
+    function pause() {
+        sendSocketMessage("controls", { "value": "pause" })
+    }
+
+    function pick_track(index) {
+        sendSocketMessage("controls", { "value": "pick-track", "index": index })
+    }
+
+    function seek(milliseconds) {
+        sendSocketMessage("controls", { "value": "seek", "position": milliseconds })
+    }
+
+    function stop() {
+        sendSocketMessage("controls", { "value": "stop" })
+    }
+
+    function clearPlaylist() {
+        root.position = ''
+        root.duration = ''
+        root.title = ''
+        root.artist = ''
+        root.cover_art = ''
+        root.old_index = -1
+        playlistview.currentIndex = -1
+    }
+
+    function time2str(value) {
+        return Qt.formatTime(new Date(value), 'mm:ss')
+    }
+}
index 15e5288..e4d5330 100644 (file)
@@ -1,7 +1,7 @@
 <RCC>
     <qresource prefix="/">
         <file>MediaPlayer.qml</file>
-        <file>api/LightMediaScanner.qml</file>
        <file>api/BluetoothManager.qml</file>
+       <file>api/MediaPlayer.qml</file>
     </qresource>
 </RCC>
index b6c1b2d..e8567c9 100644 (file)
@@ -7,7 +7,7 @@
   <author>Tasuku Suzuki &lt;tasuku.suzuki@qt.io&gt;</author>
   <license>APL 2.0</license>
   <feature name="urn:AGL:widget:required-api">
-    <param name="mediascanner" value="ws" />
+    <param name="mediaplayer" value="ws" />
     <param name="Bluetooth-Manager" value="ws" />
   </feature>
   <feature name="urn:AGL:widget:required-permission">