Commit d74a7202 authored by kempe's avatar kempe
Browse files

Refactored Audio player

* Moved Audio element to its own file
* Made player controls its own component
* Refactored sleeptimer
parent 172e2f3a
......@@ -52,5 +52,9 @@ TRANSLATIONS += translations/harbour-received-sv.ts
HEADERS +=
DISTFILES += \
qml/components/js/Rad.js
qml/components/js/Rad.js \
qml/components/DockedAudioPlayer.qml
FORMS += \
qml/components/PlayerControls.qml.ui
import QtQuick 2.0
import Sailfish.Silica 1.0
import QtMultimedia 5.0
import "./js/utils/log.js" as DebugLogger
import "./js/Storage.js" as DB
import "./js/Favorites.js" as FavoritesUtils
import "./js/Utils.js" as Utils
DockedPanel {
id: player
property var stationData: null;
property bool isFavorite: false
Audio {
id: audio
autoLoad: false
autoPlay: false
function play(station) {
if(!player.expanded)
player.open = true;
player.stationData = JSON.parse(JSON.stringify(station));
player.isFavorite = FavoritesUtils.isFavorite(stationData)
window.stationIcon = stationData.stationLogo;
stop()
audio.source = stationData.url;
audio.play()
}
function playNext() {
var nextStation = FavoritesUtils.getNextFavorite(stationData)
console.debug("Playing next favorite", JSON.stringify(nextStation))
play(nextStation)
function isPlaying() {
return playbackState == Audio.PlayingState
}
function playPrev() {
var prevStation = FavoritesUtils.getPrevFavorite(stationData)
console.debug("Playing prev favorite", JSON.stringify(prevStation))
play(prevStation)
function isLoading() {
return isPlaying() && bufferProgress < 1
}
function stop() {
audio.stopAndReset()
function stopAndReset() {
audio.stop()
audio.source = "";
}
function resume() {
audio.source = stationData.url;
audio.play()
}
onError: {
console.debug("AudioPlayer: onError: ", error)
var errorMessage = "";
function isPlaying() {
return audio.isPlaying()
}
switch (error) {
case Audio.NoError:
return;
width: parent.width
height: Theme.itemSizeExtraLarge + Theme.paddingLarge
dock: Dock.Bottom
case Audio.ResourceError:
errorMessage = "The stream URL is not valid."
break;
opacity: Qt.inputMethod.visible || !open ? 0.0 : 1.0
Behavior on opacity { FadeAnimation {duration: 300}}
case Audio.FormatError:
errorMessage = "The stream format is not supported."
break;
SleepTimer {
id: sleepTimer
case Audio.NetworkError:
errorMessage = "The network is not available."
break;
onTriggered: {
sleepTimerLabel.text = Utils.secToTimeString(sleepSec - count);
}
onSleepTriggered: {
if(player.isPlaying()) {
player.stop();
}
}
}
case Audio.AccessDenied:
errorMessage = "No permessions to access the stream."
break;
PushUpMenu {
id: playerManu
MenuLabel {
text: qsTr("Sleep timer")
}
case Audio.ServiceMissing:
errorMessage = "The audio service could not be instantiated."
break;
MenuLabelSmal {
id: sleepTimerLabel
visible: sleepTimer.running
default:
errorMessage = "An unknown error occured."
break;
}
MenuItem{
visible: sleepTimer.running
text: qsTr("Cancel")
onClicked: {
sleepTimer.stopTimer()
}
}
MenuItem {
visible: !sleepTimer.running
text: qsTr("Set")
onClicked: {
var sTime = DB.loadSleepTimer()
console.debug("DB HOUR: " + sTime.hour)
console.debug("DB MINUTE: " + sTime.minute)
var dialog = pageStack.push("Sailfish.Silica.TimePickerDialog", {
hour: sTime.hour,
minute: sTime.minute,
hourMode: DateTime.TwentyFourHours
})
dialog.accepted.connect(function() {
DB.updateSleepTimer(dialog.hour, dialog.minute)
var sec = (dialog.minute + (dialog.hour * 60)) * 60;
sleepTimer.startTimer(sec);
})
}
}
console.debug(errorMessage)
}
Item {
anchors.fill: parent
MouseArea {
id: opener
anchors.fill: parent
onClicked: console.debug("Show fullscreen player controls")
}
Row {
id: quickControlsItem
anchors.fill: parent
spacing: Theme.paddingLarge
Image {
id: stationIcon
sourceSize.height: parent.height
sourceSize.width: player.height
source: window.stationIcon
smooth: true
fillMode: Image.PreserveAspectFit
cache: true
}
Column {
id: trackInfo
width: parent.width - stationIcon.width - Theme.paddingLarge
height: parent.height
spacing: -Theme.paddingSmall
Label {
width: parent.width
truncationMode: TruncationMode.Fade
text: stationData ? stationData.name : ""
}
Label {
width: parent.width
font.pixelSize: Theme.fontSizeSmall
truncationMode: TruncationMode.Fade
color: Theme.secondaryColor
text: stationData ? stationData.genre : "";
}
ProgressBar {
id: progressBar
value: audio.bufferProgress
width: parent.width
visible: audio.isLoading()
Behavior on value {
NumberAnimation {}
}
}
Row {
id: controls
width: parent.width
property real itemWidth: width / 4
visible: !audio.isLoading()
IconButton {
width: controls.itemWidth
anchors.verticalCenter: parent.verticalCenter
icon.source: isFavorite ? "image://theme/icon-m-favorite-selected" : "image://theme/icon-m-favorite"
onClicked: {
if(!isFavorite) {
FavoritesUtils.addFavorite(stationData)
isFavorite = FavoritesUtils.isFavorite(stationData)
}
else {
FavoritesUtils.removeFavorite(stationData)
isFavorite = FavoritesUtils.isFavorite(stationData)
}
}
}
IconButton {
width: controls.itemWidth
anchors.verticalCenter: parent.verticalCenter
icon.source: "image://theme/icon-m-previous-song"
onClicked: {
playPrev()
}
}
IconButton {
width: controls.itemWidth
anchors.verticalCenter: parent.verticalCenter
icon.source: audio.isPlaying() ? "image://theme/icon-m-pause" : "image://theme/icon-m-play"
onClicked: audio.isPlaying() ? stop() : resume()
}
IconButton {
width: controls.itemWidth
anchors.verticalCenter: parent.verticalCenter
icon.source: "image://theme/icon-m-next-song"
onClicked: {
playNext()
}
}
}
}
}
onStatusChanged: {
console.debug("AudioPlayer: Status changed: ", status)
}
Audio {
id: audio
autoLoad: false
autoPlay: false
function isPlaying() {
return playbackState == Audio.PlayingState
}
function isLoading() {
return isPlaying() && bufferProgress < 1
}
function stopAndReset() {
audio.stop()
audio.source = "";
}
onError: {
console.debug("AudioPlayer: onError: ", error)
var errorMessage = "";
switch (error) {
case Audio.NoError:
return;
case Audio.ResourceError:
errorMessage = "The stream URL is not valid."
break;
case Audio.FormatError:
errorMessage = "The stream format is not supported."
break;
case Audio.NetworkError:
errorMessage = "The network is not available."
break;
case Audio.AccessDenied:
errorMessage = "No permessions to access the stream."
break;
case Audio.ServiceMissing:
errorMessage = "The audio service could not be instantiated."
break;
default:
errorMessage = "An unknown error occured."
break;
}
console.debug(errorMessage)
}
onStatusChanged: {
console.debug("AudioPlayer: Status changed: ", status)
}
onSourceChanged: {
console.debug("New source: ", source)
}
onSourceChanged: {
console.debug("New source: ", source)
}
onPlaybackStateChanged: {
console.debug("AudioPlayer PlaybackState changed: ", playbackState)
}
onPlaybackStateChanged: {
console.debug("AudioPlayer PlaybackState changed: ", playbackState)
}
}
import QtQuick 2.0
import Sailfish.Silica 1.0
import "./js/Storage.js" as DB
import "./js/Favorites.js" as FavoritesUtils
import "./js/Utils.js" as Utils
DockedPanel {
id: player
property var stationData: null;
property bool isFavorite: false
function play(station) {
if(!player.expanded)
player.open = true;
player.stationData = JSON.parse(JSON.stringify(station));
player.isFavorite = FavoritesUtils.isFavorite(stationData);
window.stationIcon = stationData.stationLogo;
stop();
audio.source = stationData.url;
audio.play();
}
function playNext() {
var nextStation = FavoritesUtils.getNextFavorite(stationData);
console.debug("Playing next favorite", JSON.stringify(nextStation));
play(nextStation);
}
function playPrev() {
var prevStation = FavoritesUtils.getPrevFavorite(stationData);
console.debug("Playing prev favorite", JSON.stringify(prevStation));
play(prevStation);
}
function stop() {
audio.stopAndReset();
}
function resume() {
audio.source = stationData.url;
audio.play();
}
function isPlaying() {
return audio.isPlaying();
}
function isLoading() {
audio.isLoading();
}
width: parent.width
height: Theme.itemSizeExtraLarge + Theme.paddingLarge
dock: Dock.Bottom
opacity: Qt.inputMethod.visible || !open ? 0.0 : 1.0
Behavior on opacity { FadeAnimation {duration: 300}}
SleepTimer {
id: sleepTimer
onSleepTriggered: player.stop();
}
PushUpMenu {
id: playerManu
MenuLabel {
text: qsTr("Sleep timer")
}
MenuLabelSmal {
id: sleepTimerLabel
visible: sleepTimer.running
text: Utils.secToTimeString(sleepTimer.progress);
}
MenuItem{
visible: sleepTimer.running
text: qsTr("Cancel")
onClicked: {
sleepTimer.stopTimer()
}
}
MenuItem {
visible: !sleepTimer.running
text: qsTr("Set")
onClicked: {
var sTime = DB.loadSleepTimer()
console.debug("DB HOUR: " + sTime.hour)
console.debug("DB MINUTE: " + sTime.minute)
var dialog = pageStack.push("Sailfish.Silica.TimePickerDialog", {
hour: sTime.hour,
minute: sTime.minute,
hourMode: DateTime.TwentyFourHours
})
dialog.accepted.connect(function() {
DB.updateSleepTimer(dialog.hour, dialog.minute)
var sec = (dialog.minute + (dialog.hour * 60)) * 60;
sleepTimer.startTimer(sec);
})
}
}
}
Item {
anchors.fill: parent
MouseArea {
id: opener
anchors.fill: parent
onClicked: console.debug("Show fullscreen player controls")
}
Row {
id: quickControlsItem
anchors.fill: parent
spacing: Theme.paddingLarge
Image {
id: stationIcon
sourceSize.height: parent.height
sourceSize.width: player.height
source: window.stationIcon
smooth: true
fillMode: Image.PreserveAspectFit
cache: true
}
Column {
id: trackInfo
width: parent.width - stationIcon.width - Theme.paddingLarge
height: parent.height
spacing: -Theme.paddingSmall
Label {
width: parent.width
truncationMode: TruncationMode.Fade
text: stationData ? stationData.name : ""
}
Label {
width: parent.width
font.pixelSize: Theme.fontSizeSmall
truncationMode: TruncationMode.Fade
color: Theme.secondaryColor
text: stationData ? stationData.genre : "";
}
ProgressBar {
id: progressBar
value: audio.bufferProgress
width: parent.width
visible: audio.isLoading()
Behavior on value {
NumberAnimation {}
}
}
PlayerControls {
id: controls
visible: !player.isLoading()
isPlaying: player.isPlaying()
isStared: player.isFavorite
buttonStar.onClicked: player.isFavorite = FavoritesUtils.toogleFavorite(stationData)
buttonPrevious.onClicked: playPrev()
buttonPlay.onClicked: player.isPlaying() ? stop() : resume()
buttonNext.onClicked: playNext()
}
}
}
}
AudioPlayer {
id: audio
}
}
import QtQuick 2.0
Row {
property bool isPlaying: false
property bool isStared: false
property alias buttonStar: buttonStar
property alias buttonPrevious: buttonPrevious
property alias buttonPlay: buttonPlay
property alias buttonNext: buttonNext
id: controls
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
property real itemWidth: width / 4
IconButton {
id: buttonStar
width: itemWidth
anchors.verticalCenter: parent.verticalCenter
icon.source: isStared ? "image://theme/icon-m-favorite-selected" : "image://theme/icon-m-favorite"
}
IconButton {
id: buttonPrevious
width: itemWidth
anchors.verticalCenter: parent.verticalCenter
icon.source: "image://theme/icon-m-previous-song"
}
IconButton {
id: buttonPlay
width: itemWidth
anchors.verticalCenter: parent.verticalCenter
icon.source: isPlaying ? "image://theme/icon-m-pause" : "image://theme/icon-m-play"
}
IconButton {
id: buttonNext
width: itemWidth
anchors.verticalCenter: parent.verticalCenter
icon.source: "image://theme/icon-m-next-song"
}
}
......@@ -3,21 +3,23 @@ import QtQuick 2.0
Timer {
property int count: 0
property int sleepSec: 0
property int progress: sleepSec - count
signal sleepTriggered()
id: sleepTimer
running: false
repeat: true
onSleepTriggered: stopTimer()
onTriggered: {
count++;
if(count >= sleepSec) {
sleepTriggered()
stop()
}
}
function stopTimer() {
stop();
stop()
count = 0;
}
......
......@@ -19,6 +19,15 @@ function updateFavorites() {
}
}
function toogleFavorite(station) {
if(isFavorite(station))