ubuntu-touch-coreapps-reviewers team mailing list archive
-
ubuntu-touch-coreapps-reviewers team
-
Mailing list archive
-
Message #07374
[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
* Bump framework to 15.04.3-qml
* Bump QtMultimedia import to 5.6
Requested reviews:
Victor Thompson (vthompson)
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
Bug #1534172 in Ubuntu Music App: "With bgplaylists if an alert sounds, the playback stops and the queue is lost"
https://bugs.launchpad.net/music-app/+bug/1534172
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 subscribed to branch lp:music-app.
=== modified file 'app/components/BlurredBackground.qml'
--- app/components/BlurredBackground.qml 2015-08-12 23:36:44 +0000
+++ app/components/BlurredBackground.qml 2016-01-15 18:03:59 +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/Flickables/MultiSelectListView.qml'
--- app/components/Flickables/MultiSelectListView.qml 2015-10-18 18:16:05 +0000
+++ app/components/Flickables/MultiSelectListView.qml 2016-01-15 18:03:59 +0000
@@ -25,6 +25,10 @@
// so we need to expose if in multiselect mode for the header states
state: ViewItems.selectMode ? "multiselectable" : "normal"
+ // Describes if model.move() should be called when a list item drag is completed
+ // this is not required on the Queue as onReorder performs playlist.moveItem()
+ property bool autoModelMove: true
+
signal clearSelection()
signal closeSelection()
signal reorder(int from, int to)
@@ -63,7 +67,10 @@
if (event.status == ListItemDrag.Moving) {
event.accept = false
} else if (event.status == ListItemDrag.Dropped) {
- model.move(event.from, event.to, 1);
+ if (autoModelMove) { // check the model should auto move
+ model.move(event.from, event.to, 1);
+ }
+
reorder(event.from, event.to)
}
}
=== modified file 'app/components/HeadState/MultiSelectHeadState.qml'
--- app/components/HeadState/MultiSelectHeadState.qml 2015-10-18 18:16:05 +0000
+++ app/components/HeadState/MultiSelectHeadState.qml 2016-01-15 18:03:59 +0000
@@ -59,14 +59,14 @@
visible: addToQueue
onTriggered: {
- var items = []
+ var items = [];
var indicies = listview.getSelectedIndices();
for (var i=0; i < indicies.length; i++) {
- items.push(listview.model.get(indicies[i], listview.model.RoleModelData));
+ items.push(Qt.resolvedUrl(listview.model.get(indicies[i], listview.model.RoleModelData).filename));
}
- trackQueue.appendList(items)
+ player.mediaPlayer.playlist.addItems(items);
listview.closeSelection()
}
=== modified file 'app/components/Helpers/ContentHubHelper.qml'
--- app/components/Helpers/ContentHubHelper.qml 2015-10-23 03:08:43 +0000
+++ app/components/Helpers/ContentHubHelper.qml 2016-01-15 18:03:59 +0000
@@ -220,14 +220,17 @@
else {
stopTimer();
- trackQueue.clear();
+ player.mediaPlayer.playlist.clearWrapper();
+
+ var items = [];
for (i=0; i < searchPaths.length; i++) {
- model = musicStore.lookup(decodeFileURI(searchPaths[i]))
-
- trackQueue.append(makeDict(model));
+ // Don't need to check if in ms2 as that is done above
+ items.push(Qt.resolvedUrl(decodeURIComponent(searchPaths[i])))
}
+ player.mediaPlayer.playlist.addItems(items);
+
trackQueueClick(0);
}
}
=== modified file 'app/components/Helpers/UriHandlerHelper.qml'
--- app/components/Helpers/UriHandlerHelper.qml 2015-08-12 23:36:44 +0000
+++ app/components/Helpers/UriHandlerHelper.qml 2016-01-15 18:03:59 +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
+ player.mediaPlayer.playlist.clearWrapper()
+ }
+
+ // enqueue
+ player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(track.filename));
+
+ // play first URI
+ if (play) {
+ trackQueueClick(player.mediaPlayer.playlist.itemCount - 1);
+ }
+ }
+
}
function process(uri, play) {
=== modified file 'app/components/Helpers/UserMetricsHelper.qml'
--- app/components/Helpers/UserMetricsHelper.qml 2015-08-12 23:36:44 +0000
+++ app/components/Helpers/UserMetricsHelper.qml 2016-01-15 18:03:59 +0000
@@ -36,21 +36,23 @@
// Connections for usermetrics
Connections {
id: userMetricPlayerConnection
- target: player
+ target: player.mediaPlayer
+
property bool 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 (player.mediaPlayer.position > 10000 && !songCounted) {
songCounted = true
songsMetric.increment()
console.debug("Increment UserMetrics")
}
}
}
+
+ Connections {
+ target: player.mediaPlayer.playlist
+ onCurrentItemSourceChanged: userMetricPlayerConnection.songCounted = false
+ }
}
=== modified file 'app/components/ListItemActions/AddToPlaylist.qml'
--- app/components/ListItemActions/AddToPlaylist.qml 2015-10-18 18:16:05 +0000
+++ app/components/ListItemActions/AddToPlaylist.qml 2016-01-15 18:03:59 +0000
@@ -25,10 +25,14 @@
objectName: "addToPlaylistAction"
text: i18n.tr("Add to playlist")
+ // Used when model can't be given to add to playlist
+ // for example in the Queue it is called metaModel not model
+ property var modelOverride: null
+
onTriggered: {
console.debug("Debug: Add track to playlist");
mainPageStack.push(Qt.resolvedUrl("../../ui/AddToPlaylist.qml"),
- {"chosenElements": [makeDict(model)]})
+ {"chosenElements": [modelOverride || makeDict(model)]})
}
}
=== modified file 'app/components/ListItemActions/AddToQueue.qml'
--- app/components/ListItemActions/AddToQueue.qml 2015-08-12 23:36:44 +0000
+++ app/components/ListItemActions/AddToQueue.qml 2016-01-15 18:03:59 +0000
@@ -29,6 +29,6 @@
onTriggered: {
console.debug("Debug: Add track to queue: " + model)
- trackQueue.append(model)
+ player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(model.filename))
}
}
=== modified file 'app/components/MusicToolbar.qml'
--- app/components/MusicToolbar.qml 2015-08-12 23:36:44 +0000
+++ app/components/MusicToolbar.qml 2016-01-15 18:03:59 +0000
@@ -18,7 +18,7 @@
*/
import QtQuick 2.4
-import QtMultimedia 5.0
+import QtMultimedia 5.6
import Ubuntu.Components 1.3
Rectangle {
@@ -42,7 +42,7 @@
anchors {
fill: parent
}
- state: trackQueue.model.count === 0 ? "disabled" : "enabled"
+ state: player.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: player.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 (player.mediaPlayer.playlist.empty) {
playRandomSong();
- }
- else {
- player.toggle();
+ } else {
+ player.mediaPlayer.toggle();
}
}
}
@@ -144,7 +143,7 @@
left: parent.left
top: parent.top
}
- covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaArt}]
+ covers: [player.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: player.currentMeta.title === ""
+ ? player.mediaPlayer.playlist.currentItemSource
+ : player.currentMeta.title
}
/* Artist of track */
@@ -185,7 +185,7 @@
elide: Text.ElideRight
fontSize: "small"
opacity: 0.4
- text: player.currentMetaArtist
+ text: player.currentMeta.author
}
}
@@ -197,9 +197,9 @@
rightMargin: units.gu(3)
verticalCenter: parent.verticalCenter
}
- color: "#FFF"
+ color: playerControlsPlayButtonMouseArea.pressed ? UbuntuColors.blue : "white"
height: units.gu(4)
- name: player.playbackState === MediaPlayer.PlayingState ?
+ name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ?
"media-playback-pause" : "media-playback-start"
objectName: "playShape"
width: height
@@ -217,12 +217,13 @@
/* Mouse area for the play button (ontop of the jump to now playing) */
MouseArea {
+ id: playerControlsPlayButtonMouseArea
anchors {
bottom: parent.bottom
horizontalCenter: playerControlsPlayButton.horizontalCenter
top: parent.top
}
- onClicked: player.toggle()
+ onClicked: player.mediaPlayer.toggle()
width: units.gu(8)
Rectangle {
@@ -260,16 +261,27 @@
}
color: UbuntuColors.blue
height: parent.height
- width: player.duration > 0 ? (player.position / player.duration) * playerControlsProgressBar.width : 0
+ width: player.mediaPlayer.progress * playerControlsProgressBar.width
+
+ // FIXME: Workaround for pad.lv/1494031 by querying gst as it does not
+ // emit until it changes to the PLAYING state. But by asking for a
+ // value we get gst to perform a query and return a result
+ // However this has to be done once the source is set, hence the delay
+ //
+ // NOTE: This does not solve when the currentIndex is removed though
+ Timer {
+ id: refreshProgressTimer
+ interval: 48
+ repeat: false
+ onTriggered: playerControlsProgressBarHint.width = player.mediaPlayer.progress * playerControlsProgressBar.width
+ }
Connections {
- target: player
- onPositionChanged: {
- playerControlsProgressBarHint.width = (player.position / player.duration) * playerControlsProgressBar.width
- }
- onStopped: {
- playerControlsProgressBarHint.width = 0;
- }
+ target: player.mediaPlayer.playlist
+ // Call timer when source or index changes
+ // so we call even if there are duplicate sources or source removal
+ onCurrentItemSourceChanged: refreshProgressTimer.start()
+ onCurrentIndexChanged: refreshProgressTimer.start()
}
}
}
=== modified file 'app/components/NowPlayingFullView.qml'
--- app/components/NowPlayingFullView.qml 2015-10-18 17:45:48 +0000
+++ app/components/NowPlayingFullView.qml 2016-01-15 18:03:59 +0000
@@ -51,7 +51,7 @@
CoverGrid {
id: albumImage
anchors.centerIn: parent
- covers: [{art: player.currentMetaArt, author: player.currentMetaArtist, album: player.currentMetaAlbum}]
+ covers: [player.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 (player.mediaPlayer.playlist.empty) {
+ ""
+ } else if (player.currentMeta.title === "") {
+ player.mediaPlayer.playlist.currentSource
+ } else {
+ player.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: player.mediaPlayer.playlist.empty ? "" : player.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()
+ player.mediaPlayer.playlist.nextWrapper()
} else if (diff > 0) {
- player.previousSong()
+ player.mediaPlayer.playlist.previousWrapper()
}
}
}
@@ -167,7 +176,7 @@
fontSize: "small"
height: parent.height
horizontalAlignment: Text.AlignHCenter
- text: durationToString(player.position)
+ text: durationToString(player.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: player.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: player.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(player.mediaPlayer.position)
}
}
@@ -203,22 +212,23 @@
if (!pressed) {
seeked = true
- player.seek(value)
+ player.mediaPlayer.seek(value)
musicToolbarFullPositionLabel.text = durationToString(value)
}
}
Connections {
- target: player
+ target: player.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(player.mediaPlayer.position)
+ musicToolbarFullDurationLabel.text = durationToString(player.mediaPlayer.duration)
- progressSliderMusic.value = player.position
- progressSliderMusic.maximumValue = player.duration
+ progressSliderMusic.value = player.mediaPlayer.position
+ // fallback to 1 when 0 so that the progress bar works
+ progressSliderMusic.maximumValue = player.mediaPlayer.duration || 1
}
progressSliderMusic.seeked = false;
@@ -240,9 +250,39 @@
fontSize: "small"
height: parent.height
horizontalAlignment: Text.AlignHCenter
- text: durationToString(player.duration)
+ text: durationToString(player.mediaPlayer.duration || 1)
verticalAlignment: Text.AlignVCenter
width: units.gu(3)
}
+
+ // FIXME: Workaround for pad.lv/1494031 by querying gst as it does not
+ // emit until it changes to the PLAYING state. But by asking for a
+ // value we get gst to perform a query and return a result
+ // However this has to be done once the source is set, hence the delay
+ //
+ // NOTE: This does not solve when the currentIndex is removed though
+ Timer {
+ id: refreshProgressTimer
+ interval: 48
+ repeat: false
+ onTriggered: {
+ if (!progressSliderMusic.seeking) {
+ musicToolbarFullPositionLabel.text = durationToString(player.mediaPlayer.position);
+ musicToolbarFullDurationLabel.text = durationToString(player.mediaPlayer.duration || 1);
+
+ progressSliderMusic.value = player.mediaPlayer.position
+ // fallback to 1 when 0 so that the progress bar works
+ progressSliderMusic.maximumValue = player.mediaPlayer.duration || 1
+ }
+ }
+ }
+
+ Connections {
+ target: player.mediaPlayer.playlist
+ // Call timer when source or index changes
+ // so we call even if there are duplicate sources or source removal
+ onCurrentItemSourceChanged: refreshProgressTimer.start()
+ onCurrentIndexChanged: refreshProgressTimer.start()
+ }
}
}
=== modified file 'app/components/NowPlayingToolbar.qml'
--- app/components/NowPlayingToolbar.qml 2015-08-12 23:36:44 +0000
+++ app/components/NowPlayingToolbar.qml 2016-01-15 18:03:59 +0000
@@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import QtMultimedia 5.0
+import QtMultimedia 5.6
import QtQuick 2.4
import Ubuntu.Components 1.3
@@ -37,7 +37,6 @@
anchors.rightMargin: units.gu(1)
anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
height: units.gu(6)
- opacity: player.repeat ? 1 : .4
width: height
onClicked: player.repeat = !player.repeat
@@ -47,23 +46,23 @@
width: height
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
- color: "white"
+ color: parent.pressed ? UbuntuColors.blue : "white"
name: "media-playlist-repeat"
objectName: "repeatShape"
- opacity: player.repeat ? 1 : .4
+ opacity: player.repeat ? 1 : .2
}
}
/* Previous button */
MouseArea {
id: nowPlayingPreviousButton
+ enabled: player.mediaPlayer.playlist.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
width: height
- onClicked: player.previousSong()
+ onClicked: player.mediaPlayer.playlist.previousWrapper()
Icon {
id: nowPlayingPreviousIndicator
@@ -71,10 +70,10 @@
width: height
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
- color: "white"
+ color: parent.pressed ? UbuntuColors.blue : "white"
name: "media-skip-backward"
objectName: "previousShape"
- opacity: 1
+ opacity: parent.enabled ? 1 : .2
}
}
@@ -84,7 +83,7 @@
anchors.centerIn: parent
height: units.gu(10)
width: height
- onClicked: player.toggle()
+ onClicked: player.mediaPlayer.toggle()
Icon {
id: nowPlayingPlayIndicator
@@ -92,8 +91,8 @@
width: height
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
- color: "white"
- name: player.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
+ color: parent.pressed ? UbuntuColors.blue : "white"
+ name: player.mediaPlayer.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
objectName: "playShape"
}
}
@@ -104,10 +103,10 @@
anchors.left: nowPlayingPlayButton.right
anchors.leftMargin: units.gu(1)
anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
+ enabled: player.mediaPlayer.playlist.canGoNext
height: units.gu(6)
- opacity: trackQueue.model.count === 0 ? .4 : 1
width: height
- onClicked: player.nextSong()
+ onClicked: player.mediaPlayer.playlist.nextWrapper()
Icon {
id: nowPlayingNextIndicator
@@ -115,10 +114,10 @@
width: height
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
- color: "white"
+ color: parent.pressed ? UbuntuColors.blue : "white"
name: "media-skip-forward"
objectName: "forwardShape"
- opacity: 1
+ opacity: parent.enabled ? 1 : .2
}
}
@@ -129,7 +128,6 @@
anchors.leftMargin: units.gu(1)
anchors.verticalCenter: nowPlayingPlayButton.verticalCenter
height: units.gu(6)
- opacity: player.shuffle ? 1 : .4
width: height
onClicked: player.shuffle = !player.shuffle
@@ -139,10 +137,10 @@
width: height
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
- color: "white"
+ color: parent.pressed ? UbuntuColors.blue : "white"
name: "media-playlist-shuffle"
objectName: "shuffleShape"
- opacity: player.shuffle ? 1 : .4
+ opacity: player.shuffle ? 1 : .2
}
}
@@ -166,17 +164,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: player.mediaPlayer.progress * playerControlsProgressBar.width
}
}
}
=== added file 'app/components/Player.qml'
--- app/components/Player.qml 1970-01-01 00:00:00 +0000
+++ app/components/Player.qml 2016-01-15 18:03:59 +0000
@@ -0,0 +1,355 @@
+/*
+ * 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.6
+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"
+
+ // For autopilot as we can't access the MediaPlayer object pad.lv/1269578
+ readonly property bool isPlaying: mediaPlayerObject.playbackState === MediaPlayer.PlayingState
+ readonly property alias count: mediaPlayerPlaylist.itemCount
+ readonly property alias currentIndex: mediaPlayerPlaylist.currentIndex
+ readonly property alias currentItemSource: mediaPlayerPlaylist.currentItemSource
+ readonly property alias position: mediaPlayerObject.position
+
+ // FIXME: pad.lv/1269578 use Item as autopilot cannot 'see' a var/QtObject
+ property alias currentMeta: currentMetaItem
+
+ property alias mediaPlayer: mediaPlayerObject
+ property alias repeat: settings.repeat
+ property alias shuffle: settings.shuffle
+
+ Item {
+ id: currentMetaItem
+ objectName: "currentMeta"
+
+ property string album: ""
+ property string art: ""
+ property string author: ""
+ property string filename: ""
+ property string title: ""
+ }
+
+ // Return the metadata for the source given from mediascanner2
+ 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
+ }
+
+ MediaPlayer {
+ id: mediaPlayerObject
+ playlist: Playlist {
+ id: mediaPlayerPlaylist
+ playbackMode: {
+ if (settings.shuffle) {
+ Playlist.Random
+ } else if (settings.repeat) {
+ Playlist.Loop
+ } else {
+ Playlist.Sequential
+ }
+ }
+
+ // as that doesn't emit changes
+ readonly property bool canGoPrevious: { // FIXME: pad.lv/1517580 use previousIndex() > -1 after mh implements it
+ currentIndex !== 0 ||
+ settings.repeat ||
+ settings.shuffle || // FIXME: pad.lv/1517580 no way to know when we are at the end of a shuffle yet
+ mediaPlayerObject.position > 5000
+ }
+ readonly property bool canGoNext: { // FIXME: pad.lv/1517580 use nextIndex() > -1 after mh implements it
+ currentIndex !== (itemCount - 1) ||
+ settings.repeat ||
+ settings.shuffle // FIXME: pad.lv/1517580 no way to know when we are at the end of a shuffle yet
+ }
+ readonly property int count: itemCount // header actions etc depend on the model having 'count'
+ readonly property bool empty: itemCount === 0
+ property int pendingCurrentIndex: -1
+ property var pendingCurrentState: null
+ property int pendingShuffle: -1
+
+ onCurrentItemSourceChanged: {
+ var meta = metaForSource(currentItemSource);
+
+ currentMeta.album = meta.album;
+ currentMeta.art = meta.art;
+ currentMeta.author = meta.author;
+ currentMeta.filename = meta.filename;
+ currentMeta.title = meta.title;
+
+ mediaPlayerObject._calcProgress();
+ }
+ onItemChanged: {
+ console.debug("*** Saving play queue in onItemChanged");
+ saveQueue()
+ }
+ onItemInserted: {
+ // When add to queue is done on an empty list currentIndex needs to be set
+ if (start === 0 && currentIndex === -1 && pendingCurrentIndex < 1 && pendingShuffle === -1) {
+ currentIndex = 0;
+
+ pendingCurrentIndex = -1;
+ processPendingCurrentState();
+ }
+
+ // Check if the pendingCurrentIndex is now valid
+ if (pendingCurrentIndex !== -1 && pendingCurrentIndex < itemCount) {
+ currentIndex = pendingCurrentIndex;
+
+ pendingCurrentIndex = -1;
+ processPendingCurrentState();
+ }
+
+ // Check if there is pending shuffle
+ // pendingShuffle holds the expected size of the model
+ if (pendingShuffle > -1 && pendingShuffle <= itemCount) {
+ pendingShuffle = -1;
+
+ nextWrapper(); // find a random track
+ mediaPlayerObject.play(); // next does not enforce play
+ }
+
+ console.debug("*** Saving play queue in onItemInserted");
+ saveQueue()
+ }
+ onItemRemoved: {
+ console.debug("*** Saving play queue in onItemRemoved");
+ saveQueue()
+ }
+
+ function addItemsFromModel(model) {
+ var items = []
+
+ for (var i=0; i < model.rowCount; i++) {
+ items.push(Qt.resolvedUrl(model.get(i, model.RoleModelData).filename));
+ }
+
+ addItems(items);
+ }
+
+ // Wrap the clear() method because we need to call stop first
+ function clearWrapper() {
+ // Stop the current playback (this ensures that play is run later)
+ if (mediaPlayerObject.playbackState === MediaPlayer.PlayingState) {
+ mediaPlayerObject.stop();
+ }
+
+ clear();
+ }
+
+ // Replicates a model.get() on a ms2 model
+ function get(index, role) {
+ return metaForSource(itemSource(index));
+ }
+
+ // Wrap the next() method so we can check canGoNext
+ function nextWrapper() {
+ if (canGoNext) {
+ next();
+ }
+ }
+
+ // Wrap the previous() method so we can check canGoPrevious
+ function previousWrapper() {
+ if (canGoPrevious) {
+ previous();
+ }
+ }
+
+ // Process the pending current PlaybackState
+ function processPendingCurrentState() {
+ if (pendingCurrentState === MediaPlayer.PlayingState) {
+ console.debug("Loading pending state play()");
+ mediaPlayerObject.play();
+ } else if (pendingCurrentState === MediaPlayer.PausedState) {
+ console.debug("Loading pending state pause()");
+ mediaPlayerObject.pause();
+ } else if (pendingCurrentState === MediaPlayer.StoppedState) {
+ console.debug("Loading pending state stop()");
+ mediaPlayerObject.stop();
+ }
+
+ pendingCurrentState = null;
+ }
+
+ // Wrapper for removeItems(from, to) so that we can use removeItems(list) until it is implemented upstream
+ function removeItemsWrapper(items) {
+ var previous = -1, end = -1;
+
+ // Sort indexes backwards so we don't have to deal with offsets when removing
+ items.sort(function(a,b) { return b-a; });
+
+ console.debug("To Remove", JSON.stringify(items));
+
+ // Merge ranges of indexes into sets of start, end points
+ // and call removeItems as we go along
+ for (var i=0; i < items.length; i++) {
+ if (end == -1) { // first value found set to first
+ end = items[i];
+ } else if (previous - 1 !== items[i]) { // set has ended (next is not 1 lower)
+ console.debug("RemoveItems", previous, end);
+ player.mediaPlayer.playlist.removeItems(previous, end);
+
+ end = items[i]; // set new high value for the next set
+ }
+
+ previous = items[i]; // last value to check if next is 1 lower
+ }
+
+ // Remove last set in list as well
+ if (items.length > 0) {
+ console.debug("RemoveItems", items[items.length - 1], end);
+ player.mediaPlayer.playlist.removeItems(items[items.length - 1], end);
+ }
+ }
+
+ function saveQueue(start, end) {
+ // FIXME: load and save do not work yet pad.lv/1510225
+ // so use our localstorage method for now
+ // save("/home/phablet/.local/share/com.ubuntu.music/queue.m3u");
+ if (mainView.loadedUI) {
+ // Don't be intelligent, just clear and rebuild the queue for now
+ Library.clearQueue();
+
+ var sources = [];
+
+ for (var i=0; i < mediaPlayerPlaylist.itemCount; i++) {
+ sources.push(mediaPlayerPlaylist.itemSource(i));
+ }
+
+ if (sources.length > 0) {
+ Library.addQueueList(sources);
+ }
+ }
+ }
+
+ function setCurrentIndex(index) {
+ // Set the currentIndex but if the itemCount is too low then wait
+ if (index < mediaPlayerPlaylist.itemCount) {
+ mediaPlayerPlaylist.currentIndex = index;
+ } else {
+ pendingCurrentIndex = index;
+ }
+ }
+
+ function setPendingCurrentState(pendingState) {
+ // Set the PlaybackState to set once pendingCurrentIndex is set
+ pendingCurrentState = pendingState;
+
+ if (pendingCurrentIndex === -1) {
+ processPendingCurrentState();
+ }
+ }
+
+ function setPendingShuffle(modelSize) {
+ // Run next() and play() when the modelSize is reached
+ if (modelSize <= itemCount) {
+ mediaPlayerPlaylist.nextWrapper(); // find a random track
+ mediaPlayerObject.play(); // next does not enforce play
+ } else {
+ pendingShuffle = modelSize;
+ }
+ }
+ }
+
+ property bool endOfMedia: false
+ property double progress: 0
+
+ onDurationChanged: _calcProgress()
+ onPositionChanged: _calcProgress()
+
+ onStatusChanged: {
+ if (status == MediaPlayer.EndOfMedia && !settings.repeat) {
+ console.debug("End of media, stopping.")
+
+ // Tells the onStopped to set the curentIndex = 0
+ endOfMedia = true;
+
+ stop();
+ }
+ }
+
+ onStopped: { // hit when pressing next() on last track with repeat off
+ console.debug("onStopped.")
+
+ // FIXME: Workaround for pad.lv/1494031 in the stopped state
+ // we do not get position/duration info so if there are items in
+ // the queue and we have stopped instead pause
+ if (playlist.itemCount > 0) {
+ // We have just ended media so jump to start of playlist
+ if (endOfMedia) {
+ playlist.currentIndex = 0;
+
+ // Play then pause otherwise when we come from EndOfMedia
+ // if calls next() until EndOfMedia again
+ play();
+ }
+
+ pause();
+ }
+
+ endOfMedia = false; // always reset endOfMedia
+ _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();
+ }
+ }
+ }
+}
=== 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 2016-01-09 12:49:43 +0000
+++ app/components/Queue.qml 2016-01-15 18:03:59 +0000
@@ -31,39 +31,43 @@
anchors {
fill: parent
}
+ autoModelMove: false // ensures we use moveItem() not move() in onReorder
footer: Item {
height: mainView.height - (styleMusic.common.expandHeight + queueList.currentHeight) + units.gu(8)
}
- model: trackQueue.model
+ model: player.mediaPlayer.playlist
objectName: "nowPlayingqueueList"
onCountChanged: customdebug("Queue: Now has: " + queueList.count + " tracks")
delegate: MusicListItem {
id: queueListItem
- color: player.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
+ color: player.mediaPlayer.playlist.currentIndex === index ? "#2c2c34" : styleMusic.mainView.backgroundColor
+ height: units.gu(7)
leadingActions: ListItemActions {
actions: [
Remove {
- onTriggered: trackQueue.removeQueueList([index])
+ onTriggered: player.mediaPlayer.playlist.removeItem(index)
}
]
}
multiselectable: true
objectName: "nowPlayingListItem" + index
+ state: ""
reorderable: true
subtitle {
objectName: "artistLabel"
- text: model.author
+ text: metaModel.author
}
title {
- color: player.currentIndex === index ? UbuntuColors.blue : styleMusic.common.music
+ color: player.mediaPlayer.playlist.currentIndex === index ? UbuntuColors.blue : styleMusic.common.music
objectName: "titleLabel"
- text: model.title
+ text: metaModel.title
}
trailingActions: ListItemActions {
actions: [
AddToPlaylist {
+ modelOverride: metaModel // model is not exposed with metadata so use metaModel
}
]
delegate: ActionDelegate {
@@ -71,24 +75,18 @@
}
}
+ property var metaModel: player.metaForSource(model.source)
+
onItemClicked: {
- customdebug("File: " + model.filename) // debugger
- trackQueueClick(index); // toggle track state
+ customdebug("File: " + model.source) // debugger
+ trackQueueClick(index);
}
}
+
onReorder: {
- Library.moveQueueItem(from, to);
-
- // 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
+ console.debug("Move: ", from, to);
+
+ player.mediaPlayer.playlist.moveItem(from, to);
}
}
=== modified file 'app/components/ViewButton/QueueAllButton.qml'
--- app/components/ViewButton/QueueAllButton.qml 2015-08-12 23:36:44 +0000
+++ app/components/ViewButton/QueueAllButton.qml 2016-01-15 18:03:59 +0000
@@ -28,7 +28,7 @@
property var model
- onClicked: addQueueFromModel(model)
+ onClicked: player.mediaPlayer.playlist.addItemsFromModel(model)
Text {
anchors {
=== modified file 'app/components/ViewButton/ShuffleButton.qml'
--- app/components/ViewButton/ShuffleButton.qml 2015-08-12 23:36:44 +0000
+++ app/components/ViewButton/ShuffleButton.qml 2016-01-15 18:03:59 +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 2016-01-15 18:03:59 +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;
+
+ // ms2 doesn't expect the URI scheme so strip file://
+ if (filename.indexOf("file://") == 0) {
+ filename = filename.substr(7);
+ }
+
+ if (musicStore.lookup(decodeFileURI(filename)) != null) {
+ res.push(Qt.resolvedUrl(filename));
}
}
});
=== modified file 'app/logic/playlists.js'
--- app/logic/playlists.js 2015-06-20 17:57:58 +0000
+++ app/logic/playlists.js 2016-01-15 18:03:59 +0000
@@ -230,6 +230,11 @@
for (j = 0; j < rs.rows.length; j++) {
var dbItem = rs.rows.item(j)
+ // ms2 doesn't expect the URI scheme so strip file://
+ if (dbItem.filename.indexOf("file://") === 0) {
+ dbItem.filename = dbItem.filename.substr(7);
+ }
+
if (musicStore.lookup(decodeFileURI(dbItem.filename)) === null) {
erroneousTracks.push(dbItem.i);
} else {
=== modified file 'app/music-app.qml'
--- app/music-app.qml 2015-12-03 14:14:14 +0000
+++ app/music-app.qml 2016-01-15 18:03:59 +0000
@@ -22,7 +22,7 @@
import Ubuntu.Components.Popups 1.3
import Ubuntu.MediaScanner 0.1
import Qt.labs.settings 1.0
-import QtMultimedia 5.0
+import QtMultimedia 5.6
import QtQuick.LocalStorage 2.0
import QtGraphicalEffects 1.0
import "logic/stored-request.js" as StoredRequest
@@ -56,6 +56,11 @@
}
}
+ Connections { // save the current queueIndex for when the app restarts
+ target: player.mediaPlayer.playlist
+ onCurrentIndexChanged: startupSettings.queueIndex = player.mediaPlayer.playlist.currentIndex
+ }
+
// Global keyboard shortcuts
focus: true
Keys.onPressed: {
@@ -73,33 +78,33 @@
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 = player.mediaPlayer.position + 10000 < player.mediaPlayer.duration
+ ? player.mediaPlayer.position + 10000 : player.mediaPlayer.duration;
+ player.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 = player.mediaPlayer.position - 10000 > 0
+ ? player.mediaPlayer.position - 10000 : 0;
+ player.mediaPlayer.seek(position);
break;
}
}
else if(event.modifiers === Qt.ControlModifier) {
switch (event.key) {
case Qt.Key_Left: // Ctrl+Left Previous Song
- player.previousSong(true);
+ player.mediaPlayer.playlist.previousWrapper();
break;
case Qt.Key_Right: // Ctrl+Right Next Song
- player.nextSong(true, true);
+ player.mediaPlayer.playlist.nextWrapper();
break;
case Qt.Key_Up: // Ctrl+Up Volume up
- player.volume = player.volume + .1 > 1 ? 1 : player.volume + .1
+ player.mediaPlayer.volume = player.mediaPlayer.volume + .1 > 1 ? 1 : player.mediaPlayer.volume + .1
break;
case Qt.Key_Down: // Ctrl+Down Volume down
- player.volume = player.volume - .1 < 0 ? 0 : player.volume - .1
+ player.mediaPlayer.volume = player.mediaPlayer.volume - .1 < 0 ? 0 : player.mediaPlayer.volume - .1
break;
case Qt.Key_R: // Ctrl+R Repeat toggle
- player.repeat = !player.repeat
+ player.mediaPlayer.repeat = !player.mediaPlayer.repeat
break;
case Qt.Key_F: // Ctrl+F Show Search popup
if (mainPageStack.currentMusicPage.searchable && mainPageStack.currentMusicPage.state === "default") {
@@ -116,13 +121,13 @@
tabs.pushNowPlaying()
break;
case Qt.Key_P: // Ctrl+P Toggle playing state
- player.toggle();
+ player.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
+ player.mediaPlayer.shuffle = !player.mediaPlayer.shuffle
break;
}
}
@@ -152,15 +157,15 @@
id: nextAction
text: i18n.tr("Next")
keywords: i18n.tr("Next Track")
- onTriggered: player.nextSong()
+ onTriggered: player.mediaPlayer.playlist.nextWrapper()
}
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: player.mediaPlayer.playbackState === MediaPlayer.PlayingState
+ ? i18n.tr("Pause") : i18n.tr("Play")
+ keywords: player.mediaPlayer.playbackState === MediaPlayer.PlayingState
+ ? i18n.tr("Pause Playback") : i18n.tr("Continue or start playback")
+ onTriggered: player.mediaPlayer.toggle()
}
Action {
id: backAction
@@ -175,13 +180,13 @@
id: prevAction
text: i18n.tr("Previous")
keywords: i18n.tr("Previous Track")
- onTriggered: player.previousSong()
+ onTriggered: player.mediaPlayer.playlist.previousWrapper()
}
Action {
id: stopAction
text: i18n.tr("Stop")
keywords: i18n.tr("Stop Playback")
- onTriggered: player.stop()
+ onTriggered: player.mediaPlayer.stop()
}
actions: [nextAction, playsAction, prevAction, stopAction, backAction]
@@ -203,23 +208,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
}
@@ -229,14 +217,29 @@
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
+
+ // FIXME: load and save do not work yet pad.lv/1510225
+ // so use our localstorage method for now
+ // player.mediaPlayer.playlist.load("/home/phablet/.local/share/com.ubuntu.music/queue.m3u")
+ // use onloaded() and onLoadFailed() to confirm it is complete
+
+ if (!Library.isQueueEmpty()) {
+ console.debug("*** Restoring library queue");
+ player.mediaPlayer.playlist.addItems(Library.getQueue());
+
+ player.mediaPlayer.playlist.setCurrentIndex(queueIndex);
+ player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PausedState);
+ }
+ else {
+ console.debug("Queue is empty, not loading any recent tracks");
+ }
}
// everything else
@@ -301,23 +304,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);
@@ -336,20 +322,13 @@
album: model.album,
art: model.art,
author: model.author,
- filename: model.filename,
+ filename: model.filename || model.source,
title: model.title
};
}
- 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;
- }
-
+ // Clear the queue, queue this model and play the specific index
+ function trackClicked(model, index, play) {
// TODO: remove once playlists uses U1DB
if (model.hasOwnProperty("linkLibraryListModel")) {
model = model.linkLibraryListModel;
@@ -358,64 +337,50 @@
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()
-
- if (!clear) {
- // If same track and on Now playing page then toggle
- if (mainPageStack.currentPage.title === i18n.tr("Now playing")
- && trackQueue.model.get(player.currentIndex) !== undefined
- && Qt.resolvedUrl(trackQueue.model.get(player.currentIndex).filename) === file) {
- player.toggle()
- return;
- }
- }
-
- trackQueue.clear(); // clear the old model
-
- addQueueFromModel(model);
+
+ player.mediaPlayer.playlist.clearWrapper(); // clear the old model
+ player.mediaPlayer.playlist.setCurrentIndex(index);
+ player.mediaPlayer.playlist.addItemsFromModel(model);
if (play) {
- player.playSong(file, index);
+ // Set the pending state for the playlist
+ // this will be set once the currentIndex has been appened to the playlist
+ player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PlayingState);
// Show the Now playing page and make sure the track is visible
tabs.pushNowPlaying();
}
- else {
- player.setSource(file);
- }
}
+ // Play or pause a current track in the queue
+ // - the index has been tapped by the user
function trackQueueClick(index) {
- if (player.currentIndex === index) {
- player.toggle();
- }
- else {
- player.playSong(trackQueue.model.get(index).filename, index);
- }
- }
-
- 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));
-
+ if (player.mediaPlayer.playlist.currentIndex === index) {
+ player.mediaPlayer.toggle();
+ } else {
+ player.mediaPlayer.playlist.setCurrentIndex(index);
+ player.mediaPlayer.playlist.setPendingCurrentState(MediaPlayer.PlayingState);
+ }
+ }
+
+ // Clear the queue and play a random track from this model
+ // - user has selected "Shuffle" in album/artists or "Tap to play random"
+ function playRandomSong(model)
+ {
+ // If no model is given use all the tracks
+ if (model === undefined) {
+ model = allSongsModel;
+ }
+
+ player.mediaPlayer.playlist.clearWrapper();
+ player.mediaPlayer.playlist.addItemsFromModel(model);
player.shuffle = true;
- trackClicked(model, index, true)
+ // Once the model count has been reached in the queue
+ // shuffle the model
+ player.mediaPlayer.playlist.setPendingShuffle(model.count);
+
+ tabs.pushNowPlaying();
}
// Wrapper function around decodeURIComponent() to prevent exceptions
@@ -453,8 +418,15 @@
var removed = []
// 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 < player.mediaPlayer.playlist.count; i++) {
+ var file = decodeFileURI(player.mediaPlayer.playlist.itemSource(i));
+
+ // ms2 doesn't expect the URI scheme so strip file://
+ if (file.indexOf("file://") === 0) {
+ file = file.substr(7);
+ }
+
+ if (musicStore.lookup(file) === null) {
removed.push(i)
}
}
@@ -462,7 +434,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)
+ player.mediaPlayer.playlist.removeItemsWrapper(removed.slice());
}
// Loop through playlists, getPlaylistTracks will remove any tracks that don't exist
@@ -529,7 +501,7 @@
if (status === SongsModel.Ready) {
// Play album it tracks exist
if (rowCount > 0 && selectedAlbum) {
- trackClicked(songsAlbumArtistModel, 0, true, true);
+ trackClicked(songsAlbumArtistModel, 0, true);
// Add album to recent list
Library.addRecent(songsAlbumArtistModel.get(0, SongsModel.RoleModelData).album, "album")
@@ -579,105 +551,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/AddToPlaylist.qml'
--- app/ui/AddToPlaylist.qml 2016-01-12 00:30:08 +0000
+++ app/ui/AddToPlaylist.qml 2016-01-15 18:03:59 +0000
@@ -17,7 +17,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-import QtMultimedia 5.0
import QtQuick 2.4
import Ubuntu.Components 1.3
import QtQuick.LocalStorage 2.0
=== modified file 'app/ui/NowPlaying.qml'
--- app/ui/NowPlaying.qml 2015-11-05 01:16:43 +0000
+++ app/ui/NowPlaying.qml 2016-01-15 18:03:59 +0000
@@ -61,12 +61,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 === player.mediaPlayer.playlist.itemCount) {
+ positionAt(player.mediaPlayer.playlist.currentIndex);
} else {
queueListLoader.item.onCountChanged.connect(function() {
- if (queueListLoader.item.count === trackQueue.model.count) {
- positionAt(player.currentIndex);
+ if (queueListLoader.item.count === player.mediaPlayer.playlist.itemCount) {
+ positionAt(player.mediaPlayer.playlist.currentIndex);
}
})
}
@@ -99,29 +99,30 @@
name: "default"
actions: [
Action {
- enabled: trackQueue.model.count > 0
+ enabled: !player.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")
+ visible: !isListView
+
onTriggered: {
var items = []
- items.push(makeDict(trackQueue.model.get(player.currentIndex)));
+ items.push(makeDict(player.metaForSource(player.mediaPlayer.playlist.currentItemSource)));
mainPageStack.push(Qt.resolvedUrl("AddToPlaylist.qml"),
{"chosenElements": items})
}
},
Action {
- enabled: trackQueue.model.count > 0
+ enabled: !player.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()
- }
+ visible: isListView
+
+ onTriggered: player.mediaPlayer.playlist.clearWrapper()
}
]
PropertyChanges {
@@ -140,13 +141,14 @@
// 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(selectedIndices.slice())
+ player.mediaPlayer.playlist.removeItemsWrapper(selectedIndices.slice());
}
}
]
Loader {
anchors {
+ bottom: nowPlayingToolbarLoader.top
left: parent.left
right: parent.right
top: parent.top
@@ -155,7 +157,6 @@
property real headerHeight: units.gu(10.125) // FIXME: 10.125 is the header.height with the page sections
- height: parent.height - headerHeight - units.gu(9.5)
source: "../components/NowPlayingFullView.qml"
visible: !isListView
}
@@ -163,8 +164,11 @@
Loader {
id: queueListLoader
anchors {
- bottomMargin: nowPlayingToolbarLoader.height + units.gu(2)
- fill: parent
+ bottom: nowPlayingToolbarLoader.top
+ bottomMargin: units.gu(2)
+ left: parent.left
+ right: parent.right
+ top: parent.top
topMargin: units.gu(2)
}
asynchronous: true
@@ -182,4 +186,13 @@
height: units.gu(10)
source: "../components/NowPlayingToolbar.qml"
}
+
+ Connections {
+ target: player.mediaPlayer.playlist
+ onEmptyChanged: {
+ if (player.mediaPlayer.playlist.empty) {
+ mainPageStack.goBack()
+ }
+ }
+ }
}
=== modified file 'app/ui/Playlists.qml'
--- app/ui/Playlists.qml 2016-01-12 00:30:08 +0000
+++ app/ui/Playlists.qml 2016-01-15 18:03:59 +0000
@@ -20,7 +20,6 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
-import QtMultimedia 5.0
import QtQuick.LocalStorage 2.0
import "../logic/playlists.js" as Playlists
import "../components"
=== modified file 'app/ui/Recent.qml'
--- app/ui/Recent.qml 2016-01-12 00:30:08 +0000
+++ app/ui/Recent.qml 2016-01-15 18:03:59 +0000
@@ -21,7 +21,6 @@
import Ubuntu.Components 1.3
import Ubuntu.MediaScanner 0.1
import Ubuntu.Thumbnailer 0.1
-import QtMultimedia 5.0
import QtQuick.LocalStorage 2.0
import "../logic/meta-database.js" as Library
import "../logic/playlists.js" as Playlists
=== modified file 'app/ui/Songs.qml'
--- app/ui/Songs.qml 2016-01-09 12:49:43 +0000
+++ app/ui/Songs.qml 2016-01-15 18:03:59 +0000
@@ -21,7 +21,6 @@
import Ubuntu.Components 1.3
import Ubuntu.MediaScanner 0.1
import Ubuntu.Thumbnailer 0.1
-import QtMultimedia 5.0
import QtQuick.LocalStorage 2.0
import "../logic/playlists.js" as Playlists
import "../components"
@@ -107,8 +106,8 @@
onItemClicked: {
if (songsPage.state === "search") { // only play single track when searching
- trackQueue.clear()
- trackQueue.append(songsModelFilter.get(index))
+ player.mediaPlayer.playlist.clearWrapper();
+ player.mediaPlayer.playlist.addItem(Qt.resolvedUrl(songsModelFilter.get(index).filename));
trackQueueClick(0)
} else {
trackClicked(songsModelFilter, index) // play track
=== modified file 'debian/changelog'
--- debian/changelog 2016-01-12 00:26:51 +0000
+++ debian/changelog 2016-01-15 18:03:59 +0000
@@ -1,9 +1,12 @@
music-app (2.3) UNRELEASED; urgency=medium
-
+
[ Andrew Hayzen ]
* Release 2.2ubuntu2 and start on 2.3
* Use ListItemLayout for listitems to improve performance and match design guidelines (LP: #1526247).
* Switch to using Qt GridView instead of custom CardView
+ * Add support for media-hub background playlists
+ * Bump framework to 15.04.3-qml
+ * Bump QtMultimedia import to 5.6
[ Ken VanDine ]
* Install the content-hub json file in the correct place for peer registry
=== modified file 'manifest.json.in'
--- manifest.json.in 2015-12-03 14:14:14 +0000
+++ manifest.json.in 2016-01-15 18:03:59 +0000
@@ -1,7 +1,7 @@
{
"architecture": "all",
"description": "A music application for ubuntu",
- "framework": "ubuntu-sdk-15.04.1-qml",
+ "framework": "ubuntu-sdk-15.04.3-qml",
"hooks": {
"music": {
"apparmor": "apparmor.json",
=== modified file 'tests/autopilot/music_app/__init__.py'
--- tests/autopilot/music_app/__init__.py 2016-01-12 00:30:08 +0000
+++ tests/autopilot/music_app/__init__.py 2016-01-15 18:03:59 +0000
@@ -54,7 +54,6 @@
# Use only objectName due to bug 1350532 as it is MainView12
self.main_view = self.app.wait_select_single(
objectName="musicMainView")
- self.player = self.app.select_single(Player, objectName='player')
def get_add_to_playlist_page(self):
return self.app.wait_select_single(AddToPlaylist,
@@ -95,8 +94,7 @@
Playlists, objectName='playlistsPage')
def get_queue_count(self):
- return self.main_view.select_single("LibraryListModel",
- objectName="trackQueue").count
+ return self.player.count
def get_songs_view(self):
return self.app.wait_select_single(SongsView, objectName="songsPage")
@@ -121,6 +119,11 @@
objectName="LoadingSpinner").running and
self.main_view.select_single("*", "allSongsModel").populated)
+ @property
+ def player(self):
+ # Get new player each time as data changes (eg currentMeta)
+ return self.app.select_single(Player, objectName='player')
+
def populate_queue(self):
tracksPage = self.get_songs_page() # switch to track tab
@@ -263,15 +266,16 @@
class Player(UbuntuUIToolkitCustomProxyObjectBase):
"""Autopilot helper for Player"""
+ @property
+ def currentMeta(self):
+ return self.select_single("*", objectName="currentMeta")
+
class NowPlaying(MusicPage):
""" Autopilot helper for now playing page """
def __init__(self, *args):
super(NowPlaying, self).__init__(*args)
- root = self.get_root_instance()
- self.player = root.select_single(Player, objectName="player")
-
self.visible.wait_for(True)
@ensure_now_playing_full
@@ -279,6 +283,9 @@
def click_forward_button(self):
return self.wait_select_single("*", objectName="forwardShape")
+ def click_full_view(self):
+ self.main_view.get_header().switch_to_section_by_index(0)
+
@ensure_now_playing_full
@click_object
def click_play_button(self):
@@ -289,6 +296,9 @@
def click_previous_button(self):
return self.wait_select_single("*", objectName="previousShape")
+ def click_queue_view(self):
+ self.main_view.get_header().switch_to_section_by_index(1)
+
@ensure_now_playing_full
@click_object
def click_repeat_button(self):
@@ -299,17 +309,21 @@
def click_shuffle_button(self):
return self.wait_select_single("*", objectName="shuffleShape")
- def click_full_view(self):
- self.main_view.get_header().switch_to_section_by_index(0)
-
- def click_queue_view(self):
- self.main_view.get_header().switch_to_section_by_index(1)
+ @click_object
+ def click_track(self, i):
+ return self.get_track(i)
@ensure_now_playing_list
def get_track(self, i):
return (self.wait_select_single(MusicListItem,
objectName="nowPlayingListItem" + str(i)))
+ @property
+ def player(self):
+ # Get new player each time as data changes (eg currentMeta)
+ root = self.get_root_instance()
+ return root.select_single(Player, objectName="player")
+
@ensure_now_playing_full
def seek_to(self, percentage):
progress_bar = self.wait_select_single(
=== modified file 'tests/autopilot/music_app/tests/test_music.py'
--- tests/autopilot/music_app/tests/test_music.py 2016-01-12 00:30:08 +0000
+++ tests/autopilot/music_app/tests/test_music.py 2016-01-15 18:03:59 +0000
@@ -77,9 +77,9 @@
self.app.populate_queue() # populate queue
# Check current meta data is correct
- self.assertThat(self.player.currentMetaTitle,
+ self.assertThat(self.player.currentMeta.title,
Eventually(Equals(self.tracks[0]["title"])))
- self.assertThat(self.player.currentMetaArtist,
+ self.assertThat(self.player.currentMeta.author,
Eventually(Equals(self.tracks[0]["artist"])))
def test_play_pause_library(self):
@@ -159,12 +159,12 @@
now_playing_page = self.app.get_now_playing_page()
# save original song data for later
- org_title = self.player.currentMetaTitle
- org_artist = self.player.currentMetaArtist
+ orig_title = self.player.currentMeta.title
+ orig_artist = self.player.currentMeta.author
# check original track
self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
- logger.debug("Original Song %s, %s" % (org_title, org_artist))
+ logger.debug("Original Song %s, %s" % (orig_title, orig_artist))
# select pause and check the player has stopped
now_playing_page.click_play_button()
@@ -174,6 +174,10 @@
# goal is to go back and forth and ensure 2 different songs
now_playing_page.click_forward_button()
+
+ # MediaPlayer Playlist does not auto play after next/previous
+ now_playing_page.click_play_button()
+
self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
# select pause and check the player has stopped
@@ -181,18 +185,22 @@
self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
# ensure different song
- self.assertThat(self.player.currentMetaTitle,
- Eventually(NotEquals(org_title)))
- self.assertThat(self.player.currentMetaArtist,
- Eventually(NotEquals(org_artist)))
+ self.assertThat(self.player.currentMeta.title,
+ Eventually(NotEquals(orig_title)))
+ self.assertThat(self.player.currentMeta.author,
+ Eventually(NotEquals(orig_artist)))
- logger.debug("Next Song %s, %s" % (self.player.currentMetaTitle,
- self.player.currentMetaArtist))
+ logger.debug("Next Song %s, %s" % (self.player.currentMeta.title,
+ self.player.currentMeta.author))
now_playing_page.seek_to(0) # seek to 0 (start)
# select previous and ensure the track is playing
now_playing_page.click_previous_button()
+
+ # MediaPlayer Playlist does not auto play after next/previous
+ now_playing_page.click_play_button()
+
self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
# select pause and check the player has stopped
@@ -200,10 +208,10 @@
self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
# ensure we're back to original song
- self.assertThat(self.player.currentMetaTitle,
- Eventually(Equals(org_title)))
- self.assertThat(self.player.currentMetaArtist,
- Eventually(Equals(org_artist)))
+ self.assertThat(self.player.currentMeta.title,
+ Eventually(Equals(orig_title)))
+ self.assertThat(self.player.currentMeta.author,
+ Eventually(Equals(orig_artist)))
def test_mp3(self):
""" Test that mp3 "plays" or at least doesn't crash on load """
@@ -231,7 +239,7 @@
Eventually(Equals(initial_tracks_count + 1)))
# Ensure the current track is mp3
- self.assertThat(self.player.source.endswith("mp3"),
+ self.assertThat(self.player.currentItemSource.endswith("mp3"),
Equals(True))
# Start playing the track (click from toolbar)
@@ -244,9 +252,9 @@
toolbar.click_play_button()
# Check current meta data is correct
- self.assertThat(self.player.currentMetaTitle,
+ self.assertThat(self.player.currentMeta.title,
Eventually(Equals(self.tracks[i]["title"])))
- self.assertThat(self.player.currentMetaArtist,
+ self.assertThat(self.player.currentMeta.author,
Eventually(Equals(self.tracks[i]["artist"])))
def test_shuffle(self):
@@ -258,30 +266,31 @@
now_playing_page = self.app.get_now_playing_page()
+ now_playing_page.set_repeat(True)
+ now_playing_page.set_shuffle(True)
+
# pause the track if it is playing
if self.player.isPlaying:
now_playing_page.click_play_button()
self.player.isPlaying.wait_for(False)
- now_playing_page.set_shuffle(True) # enable shuffle
-
- # save original song metadata
- org_title = self.player.currentMetaTitle
- org_artist = self.player.currentMetaArtist
-
- logger.debug("Original Song %s, %s" % (org_title, org_artist))
-
count = 0
+ previous_index = -1
- # loop while the track is the same if different then a shuffle occurred
- while (org_title == self.player.currentMetaTitle and
- org_artist == self.player.currentMetaArtist):
+ # Keep going until the index is not previous + 1 (with wrapping)
+ # or previous == currentIndex (to ensure shuffle is working)
+ while ((previous_index + 1) % self.player.count ==
+ self.player.currentIndex or
+ previous_index == self.player.currentIndex):
logger.debug("count %s" % (count))
# check count is valid
self.assertThat(count, LessThan(100))
+ # store this index as the previous
+ previous_index = self.player.currentIndex
+
# select next track
now_playing_page.click_forward_button()
@@ -289,28 +298,13 @@
if self.player.isPlaying:
now_playing_page.click_play_button()
- # check it is paused
- self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
-
- # save current file so we can check it goes back
- source = self.player.currentMetaFile
-
- # select previous track while will break if this previous track
- # is different and therefore a shuffle has occurred
- now_playing_page.click_previous_button()
-
- # pause the track if it is playing
- if self.player.isPlaying:
- now_playing_page.click_play_button()
-
- # check it is paused
- self.assertThat(self.player.isPlaying, Eventually(Equals(False)))
-
- # check the file has actually changed
- self.assertThat(self.player.currentMetaFile,
- Eventually(NotEquals(source)))
-
- count += 1 # increment count
+ self.player.isPlaying.wait_for(False)
+
+ # toggle shuffle to increase random
+ now_playing_page.set_shuffle(False)
+ now_playing_page.set_shuffle(True)
+
+ count += 1
def test_show_albums_page(self):
"""tests navigating to the Albums tab and displaying the album page"""
@@ -648,7 +642,7 @@
now_playing_page.click_forward_button()
# Make sure we loop back to first song after last song ends
- self.assertThat(self.player.currentMetaTitle,
+ self.assertThat(self.player.currentMeta.title,
Eventually(Equals(self.tracks[0]["title"])))
self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
@@ -667,7 +661,7 @@
now_playing_page.click_forward_button()
# Make sure we loop back to first song after last song ends
- self.assertThat(self.player.currentMetaTitle,
+ self.assertThat(self.player.currentMeta.title,
Eventually(Equals(self.tracks[0]["title"])))
self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
@@ -681,16 +675,16 @@
now_playing_page.set_shuffle(False)
now_playing_page.set_repeat(True)
- initial_song = self.player.currentMetaTitle
+ initial_song = self.player.currentMeta.title
now_playing_page.click_previous_button()
# If we're far enough into a song, pressing prev just takes us to the
# beginning of that track. In that case, hit prev again to actually
# skip over the track.
- if self.player.currentMetaTitle == initial_song:
+ if self.player.currentMeta.title == initial_song:
now_playing_page.click_previous_button()
- self.assertThat(self.player.currentMetaTitle,
+ self.assertThat(self.player.currentMeta.title,
Eventually(Equals(self.tracks[-1]["title"])))
self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
@@ -702,19 +696,23 @@
now_playing_page = self.app.get_now_playing_page()
self.player.isPlaying.wait_for(True) # ensure the track is playing
- self.player.position.wait_for(GreaterThan(5000)) # wait until > 5s
+
+ # wait until > 5s
+ self.player.position.wait_for(GreaterThan(5000))
now_playing_page.click_play_button() # pause the track
self.player.isPlaying.wait_for(False) # ensure the track has paused
- source = self.player.source # store current source
+ source = self.player.currentMeta.filename # store current source
now_playing_page.click_previous_button() # click previous
# resume the track (to ensure position updates)
now_playing_page.click_play_button()
- self.player.position.wait_for(LessThan(5000)) # wait until < 5s
+ # wait until < 5s
+ self.player.position.wait_for(LessThan(5000))
# Check that the source is the same
- self.assertThat(self.player.source, Eventually(Equals(source)))
+ self.assertThat(self.player.currentMeta.filename,
+ Eventually(Equals(source)))
Follow ups
-
[Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: noreply, 2016-01-26
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-26
-
[Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Victor Thompson, 2016-01-26
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Victor Thompson, 2016-01-26
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-26
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-25
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Victor Thompson, 2016-01-25
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Victor Thompson, 2016-01-22
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-21
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Andrew Hayzen, 2016-01-20
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-18
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Victor Thompson, 2016-01-17
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-16
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-16
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Victor Thompson, 2016-01-16
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-16
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Victor Thompson, 2016-01-16
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-16
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Victor Thompson, 2016-01-16
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-16
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Victor Thompson, 2016-01-15
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-15
-
Re: [Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Jenkins Bot, 2016-01-15
-
[Merge] lp:~music-app-dev/music-app/media-hub-bg-playlists-rework into lp:music-app
From: Andrew Hayzen, 2016-01-15