← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

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

 

Review: Needs Fixing

This is looking pretty good. I have some inline comments. The only other minor thing is to update the copyright header where appropriate with 2016.

Also, I mentioned this before: I think we need to put something in the control file to require the new media-hub and other packages be installed.

Diff comments:

> 
> === modified file 'app/components/MusicToolbar.qml'
> --- app/components/MusicToolbar.qml	2015-08-12 23:36:44 +0000
> +++ app/components/MusicToolbar.qml	2016-01-12 01:08:02 +0000
> @@ -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

Is there a reason this length of time was chosen?

> +                    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()
>                  }
>              }
>          }
> 
> === added file 'app/components/Player.qml'
> --- app/components/Player.qml	1970-01-01 00:00:00 +0000
> +++ app/components/Player.qml	2016-01-12 01:08:02 +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

Hasn't this bug been fixed and released?

> +    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) {

Consider changing to a switch statement. Since there are only 3 conditions it isn't too bad though.

> +                    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() {

Consider removing the leading "_". We don't really need to do so to denote private/internal functions. Unless we want to make the argument to start doing so.

> +            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 '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-12 01:08:02 +0000
> @@ -174,6 +174,10 @@
>  
>          # goal is to go back and forth and ensure 2 different songs
>          now_playing_page.click_forward_button()
> +
> +        # bgplaylists does not auto play after next/previous

Could you change "bgplaylists" to something more specific? MediaPlayer's playlist/queue or something?

> +        now_playing_page.click_play_button()
> +
>          self.assertThat(self.player.isPlaying, Eventually(Equals(True)))
>  
>          # select pause and check the player has stopped


-- 
https://code.launchpad.net/~music-app-dev/music-app/media-hub-bg-playlists-rework/+merge/275912
Your team Music App Developers is subscribed to branch lp:music-app.


References