Unity 8
PreviewAudioPlayback.qml
1 /*
2  * Copyright (C) 2014,2015 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.4
18 import QtMultimedia 5.0
19 import Ubuntu.Components 1.3
20 
21 /*! \brief Preview widget for audio tracks.
22 
23  This widget shows tracks contained in widgetData["tracks"], each of which should be of the form:
24 
25  \code{.json}
26  {
27  "source" "uri://to/file",
28  "title": "Title",
29  "subtitle": "Subtitle", // optional
30  "length": 125 // in seconds
31  }
32  \endcode
33  */
34 
35 PreviewWidget {
36  id: root
37  implicitHeight: childrenRect.height
38 
39  onIsCurrentPreviewChanged: if (!isCurrentPreview) audio.stop()
40 
41  Audio {
42  id: audio
43  objectName: "audio"
44 
45  property real progress: audio.position / audio.duration
46  property Item playingItem
47 
48  Component.onDestruction: {
49  // destroying the component doesn't automatically send stop to the media service, probably a bug in QtMultimedia
50  audio.stop();
51  }
52 
53  onErrorStringChanged: console.warn("Audio player error:", errorString)
54 
55  function lengthToString(s) {
56  if (typeof(s) !== "number" || s <= 0) return "";
57 
58  var sec = "" + s % 60;
59  if (sec.length == 1) sec = "0" + sec;
60  var hour = Math.floor(s / 3600);
61  if (hour < 1) {
62  return Math.floor(s / 60) + ":" + sec;
63  } else {
64  var min = "" + Math.floor(s / 60) % 60;
65  if (min.length == 1) min = "0" + min;
66  return hour + ":" + min + ":" + sec;
67  }
68  }
69  }
70 
71  Column {
72  anchors { left: parent.left; right: parent.right }
73  visible: trackRepeater.count > 0
74 
75  Repeater {
76  id: trackRepeater
77  objectName: "trackRepeater"
78  model: root.widgetData["tracks"]
79 
80  function play(item, source) {
81  audio.stop();
82  // Make sure we change the source, even if two items point to the same uri location
83  audio.source = "";
84  audio.source = source;
85  audio.playingItem = item;
86  audio.play();
87  }
88 
89  delegate: Item {
90  id: trackItem
91  objectName: "trackItem" + index
92 
93  property bool isPlayingItem: audio.playingItem == trackItem
94 
95  anchors { left: parent.left; right: parent.right }
96  height: units.gu(5)
97 
98  Row {
99  id: trackRow
100 
101  property int column1Width: units.gu(3)
102  property int column2Width: width - (2 * spacing) - column1Width - column3Width
103  property int column3Width: units.gu(4)
104 
105  anchors.verticalCenter: parent.verticalCenter
106  width: parent.width
107  spacing: units.gu(1)
108 
109  Button {
110  objectName: "playButton"
111  width: trackRow.column1Width
112  height: width
113  iconSource: audio.playbackState == Audio.PlayingState && trackItem.isPlayingItem ? "image://theme/media-playback-pause" : "image://theme/media-playback-start"
114 
115  // Can't be "transparent" or "#00xxxxxx" as the button optimizes away the surrounding shape
116  // FIXME when this is resolved: https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1251685
117  color: "#01000000"
118 
119  onClicked: {
120  if (trackItem.isPlayingItem) {
121  if (audio.playbackState == Audio.PlayingState) {
122  audio.pause();
123  } else if (audio.playbackState == Audio.PausedState) {
124  audio.play();
125  }
126  } else {
127  trackRepeater.play(trackItem, modelData["source"]);
128  }
129  }
130  }
131 
132  Item {
133  anchors.verticalCenter: parent.verticalCenter
134  width: parent.column2Width
135  height: trackSubtitleLabel.visible ? trackTitleLabel.height + trackSubtitleLabel.height : trackTitleLabel.height
136 
137  Label {
138  id: trackTitleLabel
139  objectName: "trackTitleLabel"
140  anchors { top: parent.top; left: parent.left; right: parent.right }
141  opacity: 0.9
142  color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
143  fontSize: "small"
144  horizontalAlignment: Text.AlignLeft
145  text: modelData["title"]
146  elide: Text.ElideRight
147  }
148 
149  Label {
150  id: trackSubtitleLabel
151  objectName: "trackSubtitleLabel"
152  anchors { top: trackTitleLabel.bottom; left: parent.left; right: parent.right }
153  visible: text !== ""
154  opacity: 0.9
155  color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
156  font.weight: Font.Light
157  fontSize: "small"
158  horizontalAlignment: Text.AlignLeft
159  text: modelData["subtitle"] || ""
160  elide: Text.ElideRight
161  }
162 
163  UbuntuShape {
164  id: progressBarFill
165  objectName: "progressBarFill"
166 
167  property int maxWidth: progressBarImage.width - units.dp(4)
168 
169  anchors {
170  left: progressBarImage.left
171  right: progressBarImage.right
172  verticalCenter: progressBarImage.verticalCenter
173  margins: units.dp(2)
174  rightMargin: maxWidth - (maxWidth * audio.progress) + units.dp(2)
175  }
176  height: units.dp(2)
177  visible: progressBarImage.visible
178  backgroundColor: UbuntuColors.orange
179  }
180 
181  Image {
182  id: progressBarImage
183  anchors { left: parent.left; top: parent.bottom; right: parent.right }
184  height: units.dp(6)
185  visible: audio.playbackState != Audio.StoppedState && trackItem.isPlayingItem && modelData["length"] > 0
186  source: "graphics/music_progress_bg.png"
187  }
188  }
189 
190  Label {
191  id: timeLabel
192  objectName: "timeLabel"
193  anchors.verticalCenter: parent.verticalCenter
194  width: parent.column3Width
195  opacity: 0.9
196  color: scopeStyle ? scopeStyle.foreground : theme.palette.normal.baseText
197  fontSize: "small"
198  horizontalAlignment: Text.AlignRight
199  text: audio.lengthToString(modelData["length"])
200  }
201  }
202  }
203  }
204  }
205 }