← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

[Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app

 

Andrew Hayzen has proposed merging lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app.

Commit message:
* Add support for media-hub background playlists

Requested reviews:
  Music App Developers (music-app-dev)
Related bugs:
  Bug #1251624 in Ubuntu Music App: "back button should not be random when in shuffle mode"
  https://bugs.launchpad.net/music-app/+bug/1251624
  Bug #1480280 in Ubuntu Music App: "Songs repeated when listening in shuffle mode"
  https://bugs.launchpad.net/music-app/+bug/1480280
  Bug #1500457 in Ubuntu Music App: "Pressing previous button sets duration to 0"
  https://bugs.launchpad.net/music-app/+bug/1500457

For more details, see:
https://code.launchpad.net/~music-app-dev/music-app/media-hub-bg-playlists-rework/+merge/275912

* Add support for media-hub background playlists

JUST NEED THIS FOR DIFF
-- 
Your team Music App Developers is requested to review the proposed merge of lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app.
=== modified file 'app/components/BlurredBackground.qml'
--- app/components/BlurredBackground.qml	2015-05-03 16:22:31 +0000
+++ app/components/BlurredBackground.qml	2015-10-27 19:27:27 +0000
@@ -25,7 +25,7 @@
 Item {
     width: parent.width
 
-    property string art  // : player.currentMetaFile === "" ? Qt.resolvedUrl("../graphics/music-app-cover@xxxxxx") : player.currentMetaArt
+    property string art
 
     // dark layer
     Rectangle {

=== modified file 'app/components/HeadState/MultiSelectHeadState.qml'
--- app/components/HeadState/MultiSelectHeadState.qml	2015-06-27 21:06:26 +0000
+++ app/components/HeadState/MultiSelectHeadState.qml	2015-10-27 19:27:27 +0000
@@ -41,7 +41,12 @@
                 var items = []
 
                 for (var i=0; i < listview.selectedItems.length; i++) {
-                    items.push(makeDict(listview.model.get(listview.selectedItems[i], listview.model.RoleModelData)));
+                    // TODO: improve! probably take outside of the for loop or try and make playlist accept .get()
+                    if (listview.model === newPlayer.mediaPlayer.playlist) {
+                        items.push(makeDict(newPlayer.metaForSource(listview.model.source(listview.selectedItems[i]))));
+                    } else {
+                        items.push(makeDict(listview.model.get(listview.selectedItems[i], listview.model.RoleModelData)));
+                    }
                 }
 
                 mainPageStack.push(Qt.resolvedUrl("../../ui/AddToPlaylist.qml"),
@@ -57,13 +62,13 @@
             visible: addToQueue
 
             onTriggered: {
-                var items = []
+                var sources = [];
 
                 for (var i=0; i < listview.selectedItems.length; i++) {
-                    items.push(listview.model.get(listview.selectedItems[i], listview.model.RoleModelData));
+                    sources.push(Qt.resolvedUrl(listview.model.get(listview.selectedItems[i], listview.model.RoleModelData).filename));
                 }
 
-                trackQueue.appendList(items)
+                newPlayer.mediaPlayer.playlist.addSources(sources);
 
                 listview.closeSelection()
             }

=== modified file 'app/components/Helpers/ContentHubHelper.qml'
--- app/components/Helpers/ContentHubHelper.qml	2015-08-18 21:08:05 +0000
+++ app/components/Helpers/ContentHubHelper.qml	2015-10-27 19:27:27 +0000
@@ -220,12 +220,12 @@
             else {
                 stopTimer();
 
-                trackQueue.clear();
+                newPlayer.mediaPlayer.playlist.clear();
 
                 for (i=0; i < searchPaths.length; i++) {
                     model = musicStore.lookup(decodeFileURI(searchPaths[i]))
 
-                    trackQueue.append(makeDict(model));
+                    newPlayer.mediaPlayer.playlist.addSource(Qt.resolvedUrl(decodeURIComponent(searchPaths[i])));
                 }
 
                 trackQueueClick(0);

=== modified file 'app/components/Helpers/UriHandlerHelper.qml'
--- app/components/Helpers/UriHandlerHelper.qml	2015-06-28 03:06:49 +0000
+++ app/components/Helpers/UriHandlerHelper.qml	2015-10-27 19:27:27 +0000
@@ -38,14 +38,6 @@
     }
 
     function processAlbum(uri) {
-        // Stop queue loading in the background
-        queueLoaderWorker.canLoad = false
-
-        if (queueLoaderWorker.processing > 0) {
-            waitForWorker.workerStop(queueLoaderWorker, processAlbum, [uri])
-            return;
-        }
-
         selectedAlbum = true;
         var split = uri.split("/");
 
@@ -60,34 +52,26 @@
     }
 
     function processFile(uri, play) {
-        // Stop queue loading in the background
-        queueLoaderWorker.canLoad = false
-
-        if (queueLoaderWorker.processing > 0) {
-            waitForWorker.workerStop(queueLoaderWorker, processFile, [uri, play])
-            return;
-        }
-
         // Lookup track in songs model
         var track = musicStore.lookup(decodeFileURI(uri));
 
         if (!track) {
             console.debug("Unknown file " + uri + ", skipping")
-            return;
-        }
-
-        if (play) {
-            // clear play queue
-            trackQueue.clear()
-        }
-
-        // enqueue
-        trackQueue.append(makeDict(track));
-
-        // play first URI
-        if (play) {
-            trackQueueClick(trackQueue.model.count - 1);
-        }
+        } else {
+            if (play) {
+                // clear play queue
+                newPlayer.mediaPlayer.playlist.clear()
+            }
+
+            // enqueue
+            newPlayer.mediaPlayer.playlist.addSource(Qt.resolvedUrl(track.filename));
+
+            // play first URI
+            if (play) {
+                trackQueueClick(newPlayer.mediaPlayer.playlist.mediaCount - 1);
+            }
+        }
+
     }
 
     function process(uri, play) {

=== modified file 'app/components/Helpers/UserMetricsHelper.qml'
--- app/components/Helpers/UserMetricsHelper.qml	2015-05-03 16:22:31 +0000
+++ app/components/Helpers/UserMetricsHelper.qml	2015-10-27 19:27:27 +0000
@@ -36,17 +36,15 @@
     // Connections for usermetrics
     Connections {
         id: userMetricPlayerConnection
-        target: player
+        target: newPlayer.mediaPlayer
         property bool songCounted: false
 
-        onSourceChanged: {
-            songCounted = false
-        }
+        onSourceChanged: songCounted = false
 
         onPositionChanged: {
             // Increment song count on Welcome screen if song has been
             // playing for over 10 seconds.
-            if (player.position > 10000 && !songCounted) {
+            if (newPlayer.mediaPlayer.position > 10000 && !songCounted) {
                 songCounted = true
                 songsMetric.increment()
                 console.debug("Increment UserMetrics")

=== modified file 'app/components/ListItemActions/AddToQueue.qml'
--- app/components/ListItemActions/AddToQueue.qml	2015-05-03 16:22:31 +0000
+++ app/components/ListItemActions/AddToQueue.qml	2015-10-27 19:27:27 +0000
@@ -29,6 +29,6 @@
 
     onTriggered: {
         console.debug("Debug: Add track to queue: " + model)
-        trackQueue.append(model)
+        newPlayer.mediaPlayer.playlist.addSource(Qt.resolvedUrl(model.filename))
     }
 }

=== modified file 'app/components/MusicToolbar.qml'
--- app/components/MusicToolbar.qml	2015-07-29 01:23:11 +0000
+++ app/components/MusicToolbar.qml	2015-10-27 19:27:27 +0000
@@ -42,7 +42,7 @@
         anchors {
             fill: parent
         }
-        state: trackQueue.model.count === 0 ? "disabled" : "enabled"
+        state: newPlayer.mediaPlayer.playlist.empty ? "disabled" : "enabled"
         states: [
             State {
                 name: "disabled"
@@ -104,7 +104,7 @@
                 }
                 color: "#FFF"
                 height: units.gu(4)
-                name: player.playbackState === MediaPlayer.PlayingState ?
+                name: newPlayer.mediaPlayer.playbackState === MediaPlayer.PlayingState ?
                           "media-playback-pause" : "media-playback-start"
                 objectName: "disabledSmallPlayShape"
                 width: height
@@ -116,11 +116,10 @@
                     fill: parent
                 }
                 onClicked: {
-                    if (trackQueue.model.count === 0) {
+                    if (newPlayer.mediaPlayer.playlist.empty) {
                         playRandomSong();
-                    }
-                    else {
-                        player.toggle();
+                    } else {
+                        newPlayer.mediaPlayer.toggle();
                     }
                 }
             }
@@ -144,7 +143,7 @@
                      left: parent.left
                      top: parent.top
                  }
-                 covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaArt}]
+                 covers: [newPlayer.currentMeta]
                  size: parent.height
             }
 
@@ -170,8 +169,9 @@
                     elide: Text.ElideRight
                     fontSize: "small"
                     font.weight: Font.DemiBold
-                    text: player.currentMetaTitle === ""
-                          ? player.source : player.currentMetaTitle
+                    text: newPlayer.currentMeta.title === ""
+                          ? newPlayer.mediaPlayer.playlist.currentSource
+                          : newPlayer.currentMeta.title
                 }
 
                 /* Artist of track */
@@ -185,7 +185,7 @@
                     elide: Text.ElideRight
                     fontSize: "small"
                     opacity: 0.4
-                    text: player.currentMetaArtist
+                    text: newPlayer.currentMeta.author
                 }
             }
 
@@ -199,7 +199,7 @@
                 }
                 color: "#FFF"
                 height: units.gu(4)
-                name: player.playbackState === MediaPlayer.PlayingState ?
+                name: newPlayer.mediaPlayer.playbackState === MediaPlayer.PlayingState ?
                           "media-playback-pause" : "media-playback-start"
                 objectName: "playShape"
                 width: height
@@ -222,7 +222,7 @@
                     horizontalCenter: playerControlsPlayButton.horizontalCenter
                     top: parent.top
                 }
-                onClicked: player.toggle()
+                onClicked: newPlayer.mediaPlayer.toggle()
                 width: units.gu(8)
 
                 Rectangle {
@@ -260,17 +260,17 @@
                 }
                 color: UbuntuColors.blue
                 height: parent.height
-                width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
+                width: newPlayer.mediaPlayer.progress * playerControlsProgressBar.width
+
+                /*
+                  FIXME: needed?
 
                 Connections {
-                    target: player
-                    onPositionChanged: {
-                        playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
-                    }
-                    onStopped: {
-                        playerControlsProgressBarHint.width = 0;
-                    }
+                    target: newPlayer.mediaPlayer
+                    onPositionChanged: playerControlsProgressBarHint.width = (newPlayer.mediaPlayer.position / newPlayer.mediaPlayer.duration) * playerControlsProgressBar.width
+                    onStopped: playerControlsProgressBarHint.width = 0;
                 }
+                */
             }
         }
     }

=== added file 'app/components/NewPlayer.qml'
--- app/components/NewPlayer.qml	1970-01-01 00:00:00 +0000
+++ app/components/NewPlayer.qml	2015-10-27 19:27:27 +0000
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015
+ *      Andrew Hayzen <ahayzen@xxxxxxxxx>
+ *      Victor Thompson <victor.thompson@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtMultimedia 5.4
+import QtQuick 2.4
+import Qt.labs.settings 1.0
+
+import QtQuick.LocalStorage 2.0
+import "../logic/meta-database.js" as Library
+
+Item {
+    objectName: "player"
+
+    property var currentMeta: ({})
+    property alias mediaPlayer: mediaPlayer
+    property alias repeat: settings.repeat
+    property alias shuffle: settings.shuffle
+
+    function metaForSource(source) {
+        var blankMeta = {
+            album: "",
+            art: "",
+            author: "",
+            filename: "",
+            title: ""
+        };
+
+        source = source.toString();
+
+        if (source.indexOf("file://") === 0) {
+            source = source.substring(7);
+        }
+
+        return musicStore.lookup(decodeFileURI(source)) || blankMeta;
+    }
+
+    Settings {
+        id: settings
+        category: "PlayerSettings"
+
+        property bool repeat: true
+        property bool shuffle: false
+    }
+
+    // https://code.launchpad.net/~phablet-team/kubuntu-packaging/qtmultimedia-opensource-src-playlist-support/+merge/262229
+    MediaPlayer {
+        id: mediaPlayer
+        playlist: Playlist {
+            id: mediaPlayerPlaylist
+            playbackMode: {  // FIXME: doesn't see to work
+                if (settings.shuffle) {
+                    Playlist.Random
+                } else if (settings.repeat) {
+                    Playlist.Loop
+                } else {
+                    Playlist.Sequential
+                }
+            }
+
+            onCurrentSourceChanged: currentMeta = metaForSource(currentSource)
+            onMediaChanged: {
+                saveQueue()
+
+                // FIXME: shouldn't be needed? seems to be a bug where when appending currentSourceChanged is not emitted
+                //if (start === currentIndex) {
+                //    currentMeta = metaForSource(currentSource)
+                //}
+            }
+            onMediaInserted: {
+                // When add to queue is done on an empty list currentIndex needs to be set
+                if (start === 0 && currentIndex === -1) {
+                    currentIndex = 0;
+                }
+
+                saveQueue()
+
+                // FIXME: shouldn't be needed? seems to be a bug where when appending currentSourceChanged is not emitted
+                if (start === currentIndex) {
+                    currentMeta = metaForSource(currentSource)
+                }
+            }
+            onMediaRemoved: {
+                saveQueue()
+
+                // FIXME: shouldn't be needed? seems to be a bug where when appending currentSourceChanged is not emitted
+                if (start === currentIndex) {
+                    currentMeta = metaForSource(currentSource)
+                }
+            }
+
+            // TODO: AP needs queue length
+
+            function addSourcesFromModel(model) {
+                var sources = []
+
+                for (var i=0; i < model.rowCount; i++) {
+                    sources.push(Qt.resolvedUrl(model.get(i, model.RoleModelData).filename));
+                }
+
+                addSources(sources);
+            }
+
+            function removeSources(items) {
+                items.sort();
+
+                for (var i=0; i < items.length; i++) {
+                    removeSource(items[i] - i);
+                }
+            }
+
+            function saveQueue(start, end) {
+                // TODO: should not be hardcoded
+                // FIXME: doesn't work
+                // FIXME: disabled for now to not cause errors/slow down
+                // save("/home/phablet/.local/share/com.ubuntu.music/queue.m3u");
+
+                // FIXME: using old queueList for now, move to load()/save() long term
+                if (mainView.loadedUI) {
+                    Library.clearQueue();
+
+                    var sources = [];
+
+                    for (var i=0; i < mediaPlayerPlaylist.mediaCount; i++) {
+                        sources.push(mediaPlayerPlaylist.source(i));
+                    }
+
+                    Library.addQueueList(sources);
+                }
+            }
+        }
+
+        // FIXME: Bind to settings.repeat/shuffle instead of playbackMode
+        // as that doesn't emit changes
+        property bool canGoPrevious: {
+            playlist.currentIndex !== 0 ||
+            settings.repeat ||
+            settings.shuffle
+        }
+        property bool canGoNext: {
+            playlist.currentIndex !== (playlist.mediaCount - 1) ||
+            settings.repeat ||
+            settings.shuffle
+        }
+
+        property double progress: 0
+
+        onDurationChanged: _calcProgress()
+        onPositionChanged: _calcProgress()
+
+        onStatusChanged: {
+            if (status == MediaPlayer.EndOfMedia) {
+                console.debug("End of media, stopping.")
+                playlist.currentIndex = 0;
+                stop();
+
+                _calcProgress();  // ensures progress bar has reset
+            }
+        }
+
+        onStopped: {  // hit when pressing next() on last track with repeat off
+            console.debug("onStopped.")
+            stop();
+            playlist.currentIndex = 0;
+            stop();
+
+            _calcProgress();  // ensures progress bar has reset
+        }
+
+        function _calcProgress() {
+            if (duration > 0) {
+                progress = position / duration;
+            } else if (position >= duration) {
+                progress = 0;
+            } else {
+                progress = 0;
+            }
+        }
+
+        function toggle() {
+            if (playbackState === MediaPlayer.PlayingState) {
+                pause();
+            } else {
+                play();
+            }
+        }
+    }
+}

=== modified file 'app/components/NowPlayingFullView.qml'
--- app/components/NowPlayingFullView.qml	2015-08-28 04:48:28 +0000
+++ app/components/NowPlayingFullView.qml	2015-10-27 19:27:27 +0000
@@ -51,7 +51,7 @@
             CoverGrid {
                 id: albumImage
                 anchors.centerIn: parent
-                covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]
+                covers: [newPlayer.currentMeta]
                 size: parent.height
             }
         }
@@ -92,7 +92,15 @@
                 fontSize: "x-large"
                 maximumLineCount: 2
                 objectName: "playercontroltitle"
-                text: trackQueue.model.count === 0 ? "" : player.currentMetaTitle === "" ? player.currentMetaFile : player.currentMetaTitle
+                text: {
+                    if (newPlayer.mediaPlayer.playlist.empty) {
+                        ""
+                    } else if (newPlayer.currentMeta.title === "") {
+                        newPlayer.mediaPlayer.playlist.currentSource
+                    } else {
+                        newPlayer.currentMeta.title
+                    }
+                }
                 wrapMode: Text.WordWrap
             }
 
@@ -108,7 +116,7 @@
                 color: styleMusic.nowPlaying.labelSecondaryColor
                 elide: Text.ElideRight
                 fontSize: "small"
-                text: trackQueue.model.count === 0 ? "" : player.currentMetaArtist
+                text: newPlayer.mediaPlayer.playlist.empty ? "" : newPlayer.currentMeta.author
             }
         }
 
@@ -122,12 +130,13 @@
 
             onReleased: {
                 var diff = mouse.x - lastX
+
                 if (Math.abs(diff) < units.gu(4)) {
                     return;
                 } else if (diff < 0) {
-                    player.nextSong()
+                    newPlayer.mediaPlayer.playlist.next()
                 } else if (diff > 0) {
-                    player.previousSong()
+                    newPlayer.mediaPlayer.playlist.previous()
                 }
             }
         }
@@ -167,7 +176,7 @@
             fontSize: "small"
             height: parent.height
             horizontalAlignment: Text.AlignHCenter
-            text: durationToString(player.position)
+            text: durationToString(newPlayer.mediaPlayer.position)
             verticalAlignment: Text.AlignVCenter
             width: units.gu(3)
         }
@@ -176,10 +185,10 @@
             id: progressSliderMusic
             anchors.left: parent.left
             anchors.right: parent.right
-            maximumValue: player.duration  // load value at startup
+            maximumValue: newPlayer.mediaPlayer.duration || 1  // fallback to 1 when 0 so that the progress bar works
             objectName: "progressSliderShape"
             style: UbuntuBlueSliderStyle {}
-            value: player.position  // load value at startup
+            value: newPlayer.mediaPlayer.position  // load value at startup
 
             function formatValue(v) {
                 if (seeking) {  // update position label while dragging
@@ -194,7 +203,7 @@
 
             onSeekingChanged: {
                 if (seeking === false) {
-                    musicToolbarFullPositionLabel.text = durationToString(player.position)
+                    musicToolbarFullPositionLabel.text = durationToString(newPlayer.mediaPlayer.position)
                 }
             }
 
@@ -203,22 +212,23 @@
 
                 if (!pressed) {
                     seeked = true
-                    player.seek(value)
+                    newPlayer.mediaPlayer.seek(value)
 
                     musicToolbarFullPositionLabel.text = durationToString(value)
                 }
             }
 
             Connections {
-                target: player
+                target: newPlayer.mediaPlayer
                 onPositionChanged: {
                     // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0)
                     if (progressSliderMusic.seeking === false && !progressSliderMusic.seeked) {
-                        musicToolbarFullPositionLabel.text = durationToString(player.position)
-                        musicToolbarFullDurationLabel.text = durationToString(player.duration)
+                        musicToolbarFullPositionLabel.text = durationToString(newPlayer.mediaPlayer.position)
+                        musicToolbarFullDurationLabel.text = durationToString(newPlayer.mediaPlayer.duration)
 
-                        progressSliderMusic.value = player.position
-                        progressSliderMusic.maximumValue = player.duration
+                        progressSliderMusic.value = newPlayer.mediaPlayer.position
+                        // fallback to 1 when 0 so that the progress bar works
+                        progressSliderMusic.maximumValue = newPlayer.mediaPlayer.duration || 1
                     }
 
                     progressSliderMusic.seeked = false;
@@ -240,7 +250,7 @@
             fontSize: "small"
             height: parent.height
             horizontalAlignment: Text.AlignHCenter
-            text: durationToString(player.duration)
+            text: durationToString(newPlayer.mediaPlayer.duration)
             verticalAlignment: Text.AlignVCenter
             width: units.gu(3)
         }

=== modified file 'app/components/NowPlayingToolbar.qml'
--- app/components/NowPlayingToolbar.qml	2015-07-29 01:23:11 +0000
+++ app/components/NowPlayingToolbar.qml	2015-10-27 19:27:27 +0000
@@ -37,9 +37,9 @@
         anchors.rightMargin: units.gu(1)
         anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
         height: units.gu(6)
-        opacity: player.repeat ? 1 : .4
+        opacity: newPlayer.repeat ? 1 : .4
         width: height
-        onClicked: player.repeat = !player.repeat
+        onClicked: newPlayer.repeat = !newPlayer.repeat
 
         Icon {
             id: repeatIcon
@@ -50,20 +50,21 @@
             color: "white"
             name: "media-playlist-repeat"
             objectName: "repeatShape"
-            opacity: player.repeat ? 1 : .4
+            opacity: newPlayer.repeat ? 1 : .4
         }
     }
 
     /* Previous button */
     MouseArea {
         id: nowPlayingPreviousButton
+        enabled: newPlayer.mediaPlayer.canGoPrevious
         anchors.right: nowPlayingPlayButton.left
         anchors.rightMargin: units.gu(1)
         anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
         height: units.gu(6)
-        opacity: trackQueue.model.count === 0  ? .4 : 1
+        opacity: newPlayer.mediaPlayer.playlist.empty ? .4 : 1
         width: height
-        onClicked: player.previousSong()
+        onClicked: newPlayer.mediaPlayer.playlist.previous()  // FIXME:
 
         Icon {
             id: nowPlayingPreviousIndicator
@@ -74,7 +75,7 @@
             color: "white"
             name: "media-skip-backward"
             objectName: "previousShape"
-            opacity: 1
+            opacity: parent.enabled ? 1 : .4
         }
     }
 
@@ -84,7 +85,7 @@
         anchors.centerIn: parent
         height: units.gu(10)
         width: height
-        onClicked: player.toggle()
+        onClicked: newPlayer.mediaPlayer.toggle()
 
         Icon {
             id: nowPlayingPlayIndicator
@@ -93,7 +94,7 @@
             anchors.verticalCenter: parent.verticalCenter
             anchors.horizontalCenter: parent.horizontalCenter
             color: "white"
-            name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
+            name: newPlayer.mediaPlayer.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
             objectName: "playShape"
         }
     }
@@ -104,10 +105,11 @@
         anchors.left: nowPlayingPlayButton.right
         anchors.leftMargin: units.gu(1)
         anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
+        enabled: newPlayer.mediaPlayer.canGoNext
         height: units.gu(6)
-        opacity: trackQueue.model.count === 0 ? .4 : 1
+        opacity: newPlayer.mediaPlayer.playlist.empty ? .4 : 1
         width: height
-        onClicked: player.nextSong()
+        onClicked: newPlayer.mediaPlayer.playlist.next()  // FIXME:
 
         Icon {
             id: nowPlayingNextIndicator
@@ -118,7 +120,7 @@
             color: "white"
             name: "media-skip-forward"
             objectName: "forwardShape"
-            opacity: 1
+            opacity: parent.enabled ? 1 : .4
         }
     }
 
@@ -129,9 +131,9 @@
         anchors.leftMargin: units.gu(1)
         anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
         height: units.gu(6)
-        opacity: player.shuffle ? 1 : .4
+        opacity: newPlayer.shuffle ? 1 : .4
         width: height
-        onClicked: player.shuffle = !player.shuffle
+        onClicked: newPlayer.shuffle = !newPlayer.shuffle
 
         Icon {
             id: shuffleIcon
@@ -142,7 +144,7 @@
             color: "white"
             name: "media-playlist-shuffle"
             objectName: "shuffleShape"
-            opacity: player.shuffle ? 1 : .4
+            opacity: newPlayer.shuffle ? 1 : .4
         }
     }
 
@@ -166,17 +168,7 @@
             }
             color: UbuntuColors.blue
             height: parent.height
-            width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
-
-            Connections {
-                target: player
-                onPositionChanged: {
-                    playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
-                }
-                onStopped: {
-                    playerControlsProgressBarHint.width = 0;
-                }
-            }
+            width: newPlayer.mediaPlayer.progress * playerControlsProgressBar.width
         }
     }
 }

=== removed file 'app/components/Player.qml'
--- app/components/Player.qml	2015-06-28 03:06:49 +0000
+++ app/components/Player.qml	1970-01-01 00:00:00 +0000
@@ -1,240 +0,0 @@
-/*
- * Copyright (C) 2013, 2014, 2015
- *      Andrew Hayzen <ahayzen@xxxxxxxxx>
- *      Daniel Holm <d.holmen@xxxxxxxxx>
- *      Victor Thompson <victor.thompson@xxxxxxxxx>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 3.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-import QtQuick 2.4
-import QtMultimedia 5.0
-import QtQuick.LocalStorage 2.0
-import Qt.labs.settings 1.0
-
-/*
- * This file should *only* manage the media playing and the relevant settings
- * It should therefore only access MediaPlayer, trackQueue and Settings
- * Anything else within the app should use Connections to listen for changes
- */
-
-
-Item {
-    objectName: "player"
-
-    property string currentMetaAlbum: ""
-    property string currentMetaArt: ""
-    property string currentMetaArtist: ""
-    property string currentMetaFile: ""
-    property string currentMetaTitle: ""
-    property int currentIndex: -1
-    property int duration: 1
-    readonly property bool isPlaying: player.playbackState === MediaPlayer.PlayingState
-    readonly property var playbackState: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.playbackState : MediaPlayer.StoppedState
-    property int position: 0
-    property alias repeat: settings.repeat
-    property alias shuffle: settings.shuffle
-    readonly property string source: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.source : ""
-    readonly property double volume: mediaPlayerLoader.status == Loader.Ready ? mediaPlayerLoader.item.volume : 1.0
-
-    signal stopped()
-
-    Settings {
-        id: settings
-        category: "PlayerSettings"
-
-        property bool repeat: true
-        property bool shuffle: false
-    }
-
-    Connections {
-        target: trackQueue.model
-        onCountChanged: {
-            if (trackQueue.model.count === 1 && (!queueLoaderWorker.canLoad || queueLoaderWorker.completed)) {
-                player.currentIndex = 0;
-                setSource(Qt.resolvedUrl(trackQueue.model.get(0).filename))
-            } else if (trackQueue.model.count === 0 && (!queueLoaderWorker.canLoad || queueLoaderWorker.completed)) {
-                player.currentIndex = -1
-                setSource("")
-            }
-        }
-    }
-
-    function getSong(direction, startPlaying, fromControls) {
-        // Seek to start if threshold reached when selecting previous
-        if (direction === -1 && (player.position / 1000) > 5)
-        {
-            player.seek(0);  // seek to start
-            return;
-        }
-
-        if (trackQueue.model.count == 0)
-        {
-            customdebug("No tracks in queue.");
-            return;
-        }
-
-        // default fromControls and startPlaying to true
-        fromControls = fromControls === undefined ? true : fromControls;
-        startPlaying = startPlaying === undefined ? true : startPlaying;
-        var newIndex;
-
-        console.log("currentIndex: " + currentIndex)
-        console.log("trackQueue.count: " + trackQueue.model.count)
-
-        // Do not shuffle if repeat is off and there is only one track in the queue
-        if (shuffle && !(trackQueue.model.count === 1 && !repeat)) {
-            var now = new Date();
-            var seed = now.getSeconds();
-
-            // trackQueue must be above 1 otherwise an infinite loop will occur
-            do {
-                newIndex = (Math.floor((trackQueue.model.count)
-                                       * Math.random(seed)));
-            } while (newIndex === currentIndex && trackQueue.model.count > 1)
-        } else {
-            if ((currentIndex < trackQueue.model.count - 1 && direction === 1 )
-                    || (currentIndex > 0 && direction === -1)) {
-                newIndex = currentIndex + direction
-            } else if(direction === 1 && (repeat || fromControls)) {
-                newIndex = 0
-            } else if(direction === -1 && (repeat || fromControls)) {
-                newIndex = trackQueue.model.count - 1
-            }
-            else
-            {
-                player.stop()
-                return;
-            }
-        }
-
-        if (startPlaying) {  // only start the track if told
-            playSong(trackQueue.model.get(newIndex).filename, newIndex)
-        }
-        else {
-            currentIndex = newIndex
-            setSource(Qt.resolvedUrl(trackQueue.model.get(newIndex).filename))
-        }
-
-        // Set index into queue
-        queueIndex = currentIndex
-    }
-
-    function nextSong(startPlaying, fromControls) {
-        getSong(1, startPlaying, fromControls)
-    }
-
-    function pause() {
-        mediaPlayerLoader.item.pause();
-    }
-
-    function play() {
-        mediaPlayerLoader.item.play();
-    }
-
-    function playSong(filepath, index) {
-        stop();
-        currentIndex = index;
-        queueIndex = index;
-        setSource(filepath);
-        play();
-    }
-
-    function previousSong(startPlaying) {
-        getSong(-1, startPlaying)
-    }
-
-    function seek(position) {
-        mediaPlayerLoader.item.seek(position);
-    }
-
-    function setSource(filepath) {
-        mediaPlayerLoader.item.source = Qt.resolvedUrl(filepath);
-    }
-
-    function setVolume(volume) {
-        mediaPlayerLoader.item.volume = volume
-    }
-
-    function stop() {
-        mediaPlayerLoader.item.stop();
-    }
-
-    function toggle() {
-        if (player.playbackState == MediaPlayer.PlayingState) {
-            pause()
-        }
-        else {
-            play()
-        }
-    }
-
-    Loader {
-        id: mediaPlayerLoader
-        asynchronous: true
-        sourceComponent: Component {
-            MediaPlayer {
-                muted: false
-
-                onDurationChanged: player.duration = duration
-                onPositionChanged: player.position = position
-
-                onSourceChanged: {
-                    // Force invalid source to ""
-                    if (source === undefined || source === false) {
-                        source = ""
-                        return
-                    }
-
-                    if (source.toString() === "") {
-                        player.currentIndex = -1
-                        player.stop()
-                    }
-                    else {
-                        var obj;
-
-                        if (source.toString().indexOf("file://") === 0) {
-                            obj = musicStore.lookup(decodeFileURI(source.toString().substring(7)))
-                        } else {
-                            obj = musicStore.lookup(decodeFileURI(source.toString()))
-                        }
-
-                        // protect against null reponse from the lookup
-                        if (obj !== null) {
-                            // protect against undefined properties
-                            player.currentMetaAlbum = obj.album || "";
-                            player.currentMetaArt = obj.art || "";
-                            player.currentMetaArtist = obj.author || "";
-                            player.currentMetaFile = obj.filename || "";
-                            player.currentMetaTitle = obj.title || "";
-                        } else {
-                            console.debug("Mediascanner lookup resulted in null object", source.toString())
-                        }
-                    }
-
-                    console.log("Source: " + source.toString())
-                    console.log("Index: " + player.currentIndex)
-                }
-
-                onStatusChanged: {
-                    if (status == MediaPlayer.EndOfMedia) {
-                        nextSong(true, false) // next track
-                    }
-                }
-
-                onStopped: player.stopped()
-            }
-        }
-    }
-}
-

=== modified file 'app/components/Queue.qml'
--- app/components/Queue.qml	2015-06-27 21:06:26 +0000
+++ app/components/Queue.qml	2015-10-27 19:27:27 +0000
@@ -34,7 +34,7 @@
     footer: Item {
         height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
     }
-    model: trackQueue.model
+    model: newPlayer.mediaPlayer.playlist
     objectName: "nowPlayingqueueList"
 
     property int normalHeight: units.gu(6)
@@ -44,14 +44,16 @@
 
     delegate: MusicListItem {
         id: queueListItem
-        color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
+        color: newPlayer.mediaPlayer.playlist.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
         column: Column {
+            property var metaModel: newPlayer.metaForSource(model.source)
+
             Label {
                 id: trackTitle
-                color: player.currentIndex === index ? UbuntuColors.blue : styleMusic.common.music
+                color: newPlayer.mediaPlayer.playlist.currentIndex === index ? UbuntuColors.blue : styleMusic.common.music
                 fontSize: "small"
                 objectName: "titleLabel"
-                text: model.title
+                text: metaModel.title
             }
 
             Label {
@@ -59,44 +61,53 @@
                 color: styleMusic.common.subtitle
                 fontSize: "x-small"
                 objectName: "artistLabel"
-                text: model.author
+                text: metaModel.author
             }
         }
         height: queueList.normalHeight
         objectName: "nowPlayingListItem" + index
         state: ""
         leftSideAction: Remove {
-            onTriggered: trackQueue.removeQueueList([index])
+            onTriggered: newPlayer.mediaPlayer.playlist.removeSource(index)
         }
         multiselectable: true
-        reorderable: true
+        reorderable: true  // FIXME: needs testing, sort out reordering we need moveSource(from, to);
         rightSideActions: [
             AddToPlaylist{
             }
         ]
 
         onItemClicked: {
-            customdebug("File: " + model.filename) // debugger
-            trackQueueClick(index);  // toggle track state
+            customdebug("File: " + model.source) // debugger
+            trackQueueClick(index);
         }
         onReorder: {
             console.debug("Move: ", from, to);
 
-            trackQueue.model.move(from, to, 1);
-            Library.moveQueueItem(from, to);
-
+            // Our custom reorder component does -1 on the 'to' if the item is moved 'down'
+            if (to > from) {
+                to += 1;
+            }
+
+            // If the item has gone 'up' then the remove index needs to be +1
+            // as a new item will be inserted before the item to be removed
+            if (to <= from) {
+                from += 1;
+            }
+
+            newPlayer.mediaPlayer.playlist.insertSource(to, model.source);
+            newPlayer.mediaPlayer.playlist.removeSource(from);
+
+            /*
             // Maintain currentIndex with current song
-            if (from === player.currentIndex) {
-                player.currentIndex = to;
-            }
-            else if (from < player.currentIndex && to >= player.currentIndex) {
-                player.currentIndex -= 1;
-            }
-            else if (from > player.currentIndex && to <= player.currentIndex) {
-                player.currentIndex += 1;
-            }
-
-            queueIndex = player.currentIndex
+            if (from === newPlayer.mediaPlayer.playlist.currentIndex) {
+                newPlayer.mediaPlayer.playlist.currentIndex = to;
+            } else if (from < newPlayer.mediaPlayer.playlist.currentIndex && to >= newPlayer.mediaPlayer.playlist.currentIndex) {
+                newPlayer.mediaPlayer.playlist.currentIndex -= 1;
+            } else if (from > newPlayer.mediaPlayer.playlist.currentIndex && to <= newPlayer.mediaPlayer.playlist.currentIndex) {
+                newPlayer.mediaPlayer.playlist.currentIndex += 1;
+            }
+            */
         }
     }
 }

=== modified file 'app/components/ViewButton/QueueAllButton.qml'
--- app/components/ViewButton/QueueAllButton.qml	2015-05-03 16:22:31 +0000
+++ app/components/ViewButton/QueueAllButton.qml	2015-10-27 19:27:27 +0000
@@ -28,7 +28,7 @@
 
     property var model
 
-    onClicked: addQueueFromModel(model)
+    onClicked: newPlayer.mediaPlayer.playlist.addSourcesFromModel(model)
 
     Text {
         anchors {

=== modified file 'app/components/ViewButton/ShuffleButton.qml'
--- app/components/ViewButton/ShuffleButton.qml	2015-05-03 16:22:31 +0000
+++ app/components/ViewButton/ShuffleButton.qml	2015-10-27 19:27:27 +0000
@@ -28,7 +28,7 @@
 
     property var model
 
-    onClicked: shuffleModel(model)
+    onClicked: playRandomSong(model)
 
     Text {
         anchors {

=== modified file 'app/logic/meta-database.js'
--- app/logic/meta-database.js	2015-06-20 18:01:59 +0000
+++ app/logic/meta-database.js	2015-10-27 19:27:27 +0000
@@ -70,7 +70,7 @@
         var ind = getNextIndex(tx);
 
         for (var i = 0; i < items.length; i++) {
-            tx.executeSql('INSERT OR REPLACE INTO queue (ind, filename) VALUES (?,?);', [i + ind, items[i].filename]);
+            tx.executeSql('INSERT OR REPLACE INTO queue (ind, filename) VALUES (?,?);', [i + ind, items[i]]);
         }
     }
     );
@@ -156,8 +156,15 @@
     db.transaction( function(tx) {
         var rs = tx.executeSql("SELECT * FROM queue ORDER BY ind ASC");
         for(var i = 0; i < rs.rows.length; i++) {
-            if (musicStore.lookup(decodeFileURI(rs.rows.item(i).filename)) != null) {
-                res.push(makeDict(musicStore.lookup(decodeFileURI(rs.rows.item(i).filename))));
+            var filename = rs.rows.item(i).filename;
+
+            // Strip file:// from path as ms2 only accepts /home
+            if (filename.indexOf("file://") == 0) {
+                filename = filename.substr(7);
+            }
+
+            if (musicStore.lookup(decodeFileURI(filename)) != null) {
+                res.push(Qt.resolvedUrl(filename));
             }
         }
     });

=== modified file 'app/music-app.qml'
--- app/music-app.qml	2015-09-08 08:12:31 +0000
+++ app/music-app.qml	2015-10-27 19:27:27 +0000
@@ -58,6 +58,11 @@
         }
     }
 
+    Connections {
+        target: newPlayer.mediaPlayer.playlist
+        onCurrentIndexChanged: startupSettings.queueIndex = newPlayer.mediaPlayer.playlist.currentIndex
+    }
+
     // Global keyboard shortcuts
     focus: true
     Keys.onPressed: {
@@ -75,33 +80,35 @@
 
             switch (event.key) {
             case Qt.Key_Right:  //  Alt+Right   Seek forward +10secs
-                position = player.position + 10000 < player.duration
-                        ? player.position + 10000 : player.duration;
-                player.seek(position);
+                position = newPlayer.mediaPlayer.position + 10000 < newPlayer.mediaPlayer.duration
+                        ? newPlayer.mediaPlayer.position + 10000 : newPlayer.mediaPlayer.duration;
+                newPlayer.mediaPlayer.seek(position);
                 break;
             case Qt.Key_Left:  //   Alt+Left    Seek backwards -10secs
-                position = player.position - 10000 > 0
-                        ? player.position - 10000 : 0;
-                player.seek(position);
+                position = newPlayer.mediaPlayer.position - 10000 > 0
+                        ? newPlayer.mediaPlayer.position - 10000 : 0;
+                newPlayer.mediaPlayer.seek(position);
                 break;
             }
         }
         else if(event.modifiers === Qt.ControlModifier) {
             switch (event.key) {
             case Qt.Key_Left:   //  Ctrl+Left   Previous Song
-                player.previousSong(true);
+                newPlayer.mediaPlayer.playlist.previous();
                 break;
             case Qt.Key_Right:  //  Ctrl+Right  Next Song
-                player.nextSong(true, true);
+                newPlayer.mediaPlayer.playlist.next();
                 break;
+            /*
             case Qt.Key_Up:  //     Ctrl+Up     Volume up
-                player.volume = player.volume + .1 > 1 ? 1 : player.volume + .1
+                newPlayer.mediaPlayer.volume = newPlayer.mediaPlayer.volume + .1 > 1 ? 1 : newPlayer.mediaPlayer.volume + .1
                 break;
             case Qt.Key_Down:  //   Ctrl+Down   Volume down
-                player.volume = player.volume - .1 < 0 ? 0 : player.volume - .1
+                newPlayer.mediaPlayer.volume = newPlayer.mediaPlayer.volume - .1 < 0 ? 0 : newPlayer.mediaPlayer.volume - .1
                 break;
+            */
             case Qt.Key_R:  //      Ctrl+R      Repeat toggle
-                player.repeat = !player.repeat
+                newPlayer.mediaPlayer.repeat = !newPlayer.mediaPlayer.repeat
                 break;
             case Qt.Key_F:  //      Ctrl+F      Show Search popup
                 if (mainPageStack.currentMusicPage.searchable && mainPageStack.currentMusicPage.state === "default") {
@@ -118,13 +125,13 @@
                 tabs.pushNowPlaying()
                 break;
             case Qt.Key_P:  //      Ctrl+P      Toggle playing state
-                player.toggle();
+                newPlayer.mediaPlayer.toggle();
                 break;
             case Qt.Key_Q:  //      Ctrl+Q      Quit the app
                 Qt.quit();
                 break;
             case Qt.Key_U:  //      Ctrl+U      Shuffle toggle
-                player.shuffle = !player.shuffle
+                newPlayer.mediaPlayer.shuffle = !newPlayer.mediaPlayer.shuffle
                 break;
             }
         }
@@ -154,15 +161,15 @@
         id: nextAction
         text: i18n.tr("Next")
         keywords: i18n.tr("Next Track")
-        onTriggered: player.nextSong()
+        onTriggered: newPlayer.mediaPlayer.playlist.next()
     }
     Action {
         id: playsAction
-        text: player.playbackState === MediaPlayer.PlayingState ?
-                  i18n.tr("Pause") : i18n.tr("Play")
-        keywords: player.playbackState === MediaPlayer.PlayingState ?
-                      i18n.tr("Pause Playback") : i18n.tr("Continue or start playback")
-        onTriggered: player.toggle()
+        text: newPlayer.mediaPlayer.playbackState === MediaPlayer.PlayingState
+              ? i18n.tr("Pause") : i18n.tr("Play")
+        keywords: newPlayer.mediaPlayer.playbackState === MediaPlayer.PlayingState
+                  ? i18n.tr("Pause Playback") : i18n.tr("Continue or start playback")
+        onTriggered: newPlayer.mediaPlayer.toggle()
     }
     Action {
         id: backAction
@@ -177,16 +184,18 @@
         id: prevAction
         text: i18n.tr("Previous")
         keywords: i18n.tr("Previous Track")
-        onTriggered: player.previousSong()
+        onTriggered: newPlayer.mediaPlayer.playlist.previous()
     }
+    /*
     Action {
         id: stopAction
         text: i18n.tr("Stop")
         keywords: i18n.tr("Stop Playback")
-        onTriggered: player.stop()
+        onTriggered: newPlayer.mediaPlayer.stop()
     }
+    */
 
-    actions: [nextAction, playsAction, prevAction, stopAction, backAction]
+    actions: [nextAction, playsAction, prevAction, backAction]
 
     UriHandlerHelper {
         id: uriHandler
@@ -205,23 +214,6 @@
     width: units.gu(100)
     height: units.gu(80)
 
-    WorkerModelLoader {
-        id: queueLoaderWorker
-        canLoad: false
-        model: trackQueue.model
-        syncFactor: 10
-
-        onPreLoadCompleteChanged: {
-            if (preLoadComplete) {
-                player.currentIndex = queueIndex
-
-                if (list[queueIndex] !== undefined) {
-                    player.setSource(list[queueIndex].filename)
-                }
-            }
-        }
-    }
-
     WorkerWaiter {
         id: waitForWorker
     }
@@ -231,14 +223,27 @@
         customdebug("Version "+appVersion) // print the curren version
 
         Library.createRecent()  // initialize recent
+        Library.createQueue()  // create queue if it doesn't exist
 
         // initialize playlists
         Playlists.initializePlaylist()
 
         if (!args.values.url) {
-            // allow the queue loader to start
-            queueLoaderWorker.canLoad = !Library.isQueueEmpty()
-            queueLoaderWorker.list = Library.getQueue()
+            // load the previous queue as there are no args
+            // TODO: should not be hardcoded
+            // FIXME: doesn't work
+            //newPlayer.mediaPlayer.playlist.load("/home/phablet/.local/share/com.ubuntu.music/queue.m3u")
+
+            // use onloaded() and onLoadFailed() to confirm it is complete
+            // TODO: test if sync/async as future events may change the queue
+
+            // FIXME: using old queueList for now, move to load()/save() long term
+            if (!Library.isQueueEmpty()) {
+                newPlayer.mediaPlayer.playlist.addSources(Library.getQueue());
+
+                newPlayer.mediaPlayer.playlist.currentIndex = queueIndex;
+                newPlayer.mediaPlayer.pause();
+            }
         }
 
         // everything else
@@ -307,23 +312,6 @@
         }
     }
 
-    function addQueueFromModel(model)
-    {
-        // TODO: remove once playlists uses U1DB
-        if (model.hasOwnProperty("linkLibraryListModel")) {
-            model = model.linkLibraryListModel;
-        }
-
-        var items = []
-
-        for (var i=0; i < model.rowCount; i++) {
-            items.push(model.get(i, model.RoleModelData))
-        }
-
-        // Add model to queue storage
-        trackQueue.appendList(items)
-    }
-
     // Converts an duration in ms to a formated string ("minutes:seconds")
     function durationToString(duration) {
         var minutes = Math.floor((duration/1000) / 60);
@@ -348,14 +336,6 @@
     }
 
     function trackClicked(model, index, play, clear) {
-        // Stop queue loading in the background
-        queueLoaderWorker.canLoad = false
-
-        if (queueLoaderWorker.processing > 0) {
-            waitForWorker.workerStop(queueLoaderWorker, trackClicked, [model, index, play, clear])
-            return;
-        }
-
         // TODO: remove once playlists uses U1DB
         if (model.hasOwnProperty("linkLibraryListModel")) {
             model = model.linkLibraryListModel;
@@ -364,39 +344,35 @@
         var file = Qt.resolvedUrl(model.get(index, model.RoleModelData).filename);
 
         play = play === undefined ? true : play  // default play to true
-        clear = clear === undefined ? false : clear  // force clear and will ignore player.toggle()
+        clear = clear === undefined ? false : clear  // force clear and will ignore toggle()
 
-        if (!clear) {
+        if (!clear) {  // FIXME: is this even used anymore? trackQueueClick instead?
             // If same track and on Now playing page then toggle
             if ((mainPageStack.currentPage.title === i18n.tr("Now playing") || mainPageStack.currentPage.title === i18n.tr("Queue"))
-                    && trackQueue.model.get(player.currentIndex) !== undefined
-                    && Qt.resolvedUrl(trackQueue.model.get(player.currentIndex).filename) === file) {
-                player.toggle()
+                    && Qt.resolvedUrl(newPlayer.mediaPlayer.playlist.currentSource) === file) {
+                newPlayer.mediaPlayer.toggle()
                 return;
             }
         }
 
-        trackQueue.clear();  // clear the old model
-
-        addQueueFromModel(model);
+        newPlayer.mediaPlayer.playlist.clear();  // clear the old model
+        newPlayer.mediaPlayer.playlist.addSourcesFromModel(model);
+        newPlayer.mediaPlayer.playlist.currentIndex = index;
 
         if (play) {
-            player.playSong(file, index);
+            newPlayer.mediaPlayer.play();
 
             // Show the Now playing page and make sure the track is visible
             tabs.pushNowPlaying();
         }
-        else {
-            player.setSource(file);
-        }
     }
 
     function trackQueueClick(index) {
-        if (player.currentIndex === index) {
-            player.toggle();
-        }
-        else {
-            player.playSong(trackQueue.model.get(index).filename, index);
+        if (newPlayer.mediaPlayer.playlist.currentIndex === index) {
+            newPlayer.mediaPlayer.toggle();
+        }  else {
+            newPlayer.mediaPlayer.playlist.currentIndex = index;
+            newPlayer.mediaPlayer.play();
         }
 
         // Show the Now playing page and make sure the track is visible
@@ -405,28 +381,17 @@
         }
     }
 
-    function playRandomSong(shuffle)
-    {
-        trackQueue.clear();
-
-        var now = new Date();
-        var seed = now.getSeconds();
-        var index = Math.floor(allSongsModel.rowCount * Math.random(seed));
-
-        player.shuffle = shuffle === undefined ? true : shuffle;
-
-        trackClicked(allSongsModel, index, true)
-    }
-
-    function shuffleModel(model)
-    {
-        var now = new Date();
-        var seed = now.getSeconds();
-        var index = Math.floor(model.count * Math.random(seed));
-
-        player.shuffle = true;
-
-        trackClicked(model, index, true)
+    function playRandomSong(model)
+    {
+        if (model === undefined) {
+            model = allSongsModel;
+        }
+
+        newPlayer.mediaPlayer.playlist.clear();
+        newPlayer.mediaPlayer.playlist.addSourcesFromModel(model);
+        newPlayer.shuffle = true;
+        newPlayer.mediaPlayer.playlist.shuffle();
+        newPlayer.mediaPlayer.play();
     }
 
     // Wrapper function around decodeURIComponent() to prevent exceptions
@@ -463,9 +428,11 @@
                 var i
                 var removed = []
 
+                // TODO: needs testing
+
                 // Find tracks from the queue that aren't in ms2 anymore
-                for (i=0; i < trackQueue.model.count; i++) {
-                    if (musicStore.lookup(decodeFileURI(trackQueue.model.get(i).filename)) === null) {
+                for (i=0; i < newPlayer.mediaPlayer.playlist.count; i++) {
+                    if (musicStore.lookup(decodeFileURI(newPlayer.mediaPlayer.playlist.source(i).filename)) === null) {
                         removed.push(i)
                     }
                 }
@@ -473,7 +440,7 @@
                 // If there are removed tracks then remove them from the queue and store
                 if (removed.length > 0) {
                     console.debug("Removed queue:", JSON.stringify(removed))
-                    trackQueue.removeQueueList(removed)
+                    newPlayer.mediaPlayer.playlist.removeSources(removed.slice());
                 }
 
                 // Loop through playlists, getPlaylistTracks will remove any tracks that don't exist
@@ -559,8 +526,8 @@
     }
 
     // WHERE THE MAGIC HAPPENS
-    Player {
-        id: player
+    NewPlayer {
+        id: newPlayer
     }
 
     // TODO: Used by playlisttracks move to U1DB
@@ -590,105 +557,6 @@
         }
     }
 
-    // list of tracks on startup. This is just during development
-    LibraryListModel {
-        id: trackQueue
-        objectName: "trackQueue"
-
-        function append(listElement)
-        {
-            model.append(makeDict(listElement))
-            Library.addQueueItem(listElement.filename)
-        }
-
-        function appendList(items)
-        {
-            for (var i=0; i < items.length; i++) {
-                model.append(makeDict(items[i]))
-            }
-
-            Library.addQueueList(items)
-        }
-
-        function clear()
-        {
-            Library.clearQueue()
-            model.clear()
-
-            queueIndex = 0  // reset otherwise when you append and play 1 track it doesn't update correctly
-        }
-
-        // Optimised removeQueue for removing multiple tracks from the queue
-        function removeQueueList(items)
-        {
-            var i;
-
-            // Remove from the saved queue database
-            Library.removeQueueList(items)
-
-            // Sort the item indexes as loops below assume *numeric* sort
-            items.sort(function(a,b) { return a - b })
-
-            // Remove from the listmodel
-            var startCount = trackQueue.model.count
-
-            for (i=0; i < items.length; i++) {
-                // use diff in count as sometimes the row is removed from the model
-                trackQueue.model.remove(items[i] - (startCount - trackQueue.model.count));
-            }
-
-            // Update the currentIndex and playing status
-
-            if (trackQueue.model.count === 0) {
-                // Nothing in the queue so stop and pop the queue
-                player.stop()
-                mainPageStack.goBack()
-            } else if (items.indexOf(player.currentIndex) > -1) {
-                // Current track was removed
-
-                var newIndex;
-
-                // Find the first index that still exists before the currentIndex
-                for (i=player.currentIndex - 1; i > -1; i--) {
-                    if (items.indexOf(i) === -1) {
-                        break;
-                    }
-                }
-
-                newIndex = i;
-
-                // Shuffle index down as tracks were removed before the current
-                for (i=newIndex; i > -1; i--) {
-                    if (items.indexOf(i) !== -1) {
-                        newIndex--;
-                    }
-                }
-
-                // Set this as the current track
-                player.currentIndex = newIndex
-
-                // Play the next track
-                player.nextSong(player.isPlaying);
-            } else {
-                // Current track not in removed list
-                // Check if the index needs to be shuffled down due to removals
-
-                var before = 0
-
-                for (i=0; i < items.length; i++) {
-                    if (items[i] < player.currentIndex) {
-                        before++;
-                    }
-                }
-
-                // Update the index
-                player.currentIndex -= before;
-            }
-
-            queueIndex = player.currentIndex;  // ensure saved index is up to date
-        }
-    }
-
     // TODO: list of playlists move to U1DB
     // create the listmodel to use for playlists
     LibraryListModel {

=== modified file 'app/ui/NowPlaying.qml'
--- app/ui/NowPlaying.qml	2015-08-19 14:03:16 +0000
+++ app/ui/NowPlaying.qml	2015-10-27 19:27:27 +0000
@@ -56,12 +56,12 @@
 
     // Ensure that the listview has loaded before attempting to positionAt
     function ensureListViewLoaded() {
-        if (queueListLoader.item.count === trackQueue.model.count) {
-            positionAt(player.currentIndex);
+        if (queueListLoader.item.count === newPlayer.mediaPlayer.playlist.mediaCount) {
+            positionAt(newPlayer.mediaPlayer.playlist.currentIndex);
         } else {
             queueListLoader.item.onCountChanged.connect(function() {
-                if (queueListLoader.item.count === trackQueue.model.count) {
-                    positionAt(player.currentIndex);
+                if (queueListLoader.item.count === newPlayer.mediaPlayer.playlist.mediaCount) {
+                    positionAt(newPlayer.mediaPlayer.playlist.currentIndex);
                 }
             })
         }
@@ -87,28 +87,30 @@
                     }
                 },
                 Action {
-                    enabled: trackQueue.model.count > 0
+                    enabled: !newPlayer.mediaPlayer.playlist.empty
                     iconName: "add-to-playlist"
                     // TRANSLATORS: this action appears in the overflow drawer with limited space (around 18 characters)
                     text: i18n.tr("Add to playlist")
                     onTriggered: {
                         var items = []
 
-                        items.push(makeDict(trackQueue.model.get(player.currentIndex)));
+                        items.push(makeDict(newPlayer.metaForSource(newPlayer.mediaPlayer.playlist.currentSource)));
 
                         mainPageStack.push(Qt.resolvedUrl("AddToPlaylist.qml"),
                                            {"chosenElements": items})
                     }
                 },
                 Action {
-                    enabled: trackQueue.model.count > 0
+                    enabled: !newPlayer.mediaPlayer.playlist.empty
                     iconName: "delete"
                     objectName: "clearQueue"
                     // TRANSLATORS: this action appears in the overflow drawer with limited space (around 18 characters)
                     text: i18n.tr("Clear queue")
                     onTriggered: {
                         mainPageStack.goBack()
-                        trackQueue.clear()
+                        newPlayer.mediaPlayer.playlist.clear()
+
+                        // FIXME: stop audio?
                     }
                 }
             ]
@@ -128,7 +130,9 @@
                 // Remove the tracks from the queue
                 // Use slice() to copy the list
                 // so that the indexes don't change as they are removed
-                trackQueue.removeQueueList(selectedItems.slice())
+
+                // TODO: test!
+                newPlayer.mediaPlayer.playlist.removeSources(selectedItems.slice());
             }
         }
     ]

=== modified file 'app/ui/Songs.qml'
--- app/ui/Songs.qml	2015-06-27 21:06:26 +0000
+++ app/ui/Songs.qml	2015-10-27 19:27:27 +0000
@@ -122,8 +122,8 @@
 
             onItemClicked: {
                 if (songsPage.state === "search") {  // only play single track when searching
-                    trackQueue.clear()
-                    trackQueue.append(songsModelFilter.get(index))
+                    newPlayer.mediaPlayer.playlist.clear();
+                    newPlayer.mediaPlayer.playlist.addSource(Qt.resolvedUrl(songsModelFilter.get(index).filename));
                     trackQueueClick(0)
                 } else {
                     trackClicked(songsModelFilter, index)  // play track

=== modified file 'debian/changelog'
--- debian/changelog	2015-09-25 08:11:22 +0000
+++ debian/changelog	2015-10-27 19:27:27 +0000
@@ -1,5 +1,8 @@
 music-app (2.2ubuntu2) UNRELEASED; urgency=medium
 
+  [ Andrew Hayzen ]
+  * Add support for media-hub background playlists
+
   [ Bartosz Kosiorek ]
   *  Reduce size of images (with tinypng.com) to decrease click size and improve performance
 


References