mediaplayer: fix race condition with local media and bluetooth a2dp
[apps/mediaplayer.git] / app / MediaPlayer.qml
1 /*
2  * Copyright (C) 2016 The Qt Company Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 import QtQuick 2.6
18 import QtQuick.Layouts 1.1
19 import QtQuick.Controls 2.0
20 import QtMultimedia 5.6
21 import AGL.Demo.Controls 1.0
22
23 ApplicationWindow {
24     id: root
25
26     Item {
27         id: player
28
29         property string title: ""
30         property string album: ""
31         property string artist: ""
32
33         property int duration: 0
34         property int position: 0
35
36         property string cover_art: ""
37         property string status: "stopped"
38
39         function time2str(value) {
40             return Qt.formatTime(new Date(value), 'mm:ss')
41         }
42     }
43
44     Item {
45         id: bluetooth
46
47         property string deviceAddress: ""
48         property bool connected: false
49         property bool av_connected: false
50
51         property int position: 0
52         property int duration: 0
53
54         property string artist: ""
55         property string title: ""
56         property string state: "stopped"
57
58         // AVRCP Target UUID
59         property string avrcp_uuid: "0000110e-0000-1000-8000-00805f9b34fb"
60
61         function connect_profiles() {
62             var address = bluetooth.deviceAddress;
63             bluetooth_connection.connect(address, "a2dp")
64             bluetooth_connection.connect(address, "avrcp")
65         }
66
67         function disconnect_profiles() {
68             var address = bluetooth.deviceAddress;
69             bluetooth_connection.disconnect(address, "a2dp")
70             bluetooth_connection.disconnect(address, "avrcp")
71         }
72
73         function set_avrcp_controls(cmd) {
74             bluetooth_connection.set_avrcp_controls(bluetooth.deviceAddress, cmd)
75         }
76     }
77
78     Connections {
79         target: bluetooth_connection
80
81         onDeviceListEvent: {
82             var address = ""
83             for (var i = 0; i < data.list.length; i++) {
84                 var item = data.list[i]
85                 if (item.Connected == "True" && item.UUIDs.indexOf(bluetooth.avrcp_uuid) >= 0) {
86                     address = item.Address
87
88                     bluetooth.connected = true
89                     mediaplayer.pause()
90
91                     //NOTE: This hack is here for when MediaPlayer is started
92                     //      with an existing connection.
93                     bluetooth.av_connected = item.AVPConnected == "True" 
94                 }
95             }
96             if (!address)
97                 bluetooth.connected = false
98             else
99                 bluetooth.deviceAddress = address
100         }
101
102         onDeviceUpdatedEvent: {
103             var metadata = data.Metadata
104
105             if (data.Connected == "False")
106                 return
107
108             if (!bluetooth.av_connected && data.AVPConnected == "True") {
109                 mediaplayer.pause()
110                 player.status = "stopped"
111             }
112
113             bluetooth.connected = data.Connected == "True"
114             bluetooth.av_connected = data.AVPConnected == "True"
115             bluetooth.deviceAddress = data.Address
116
117             if ('Position' in metadata)
118                 bluetooth.position = metadata.Position
119
120             if ('Duration' in metadata)
121                 bluetooth.duration = metadata.Duration
122
123             if ('Status' in metadata)
124                 bluetooth.state = metadata.Status
125
126             if ('Artist' in metadata)
127                 bluetooth.artist = metadata.Artist
128
129             if ('Title' in metadata)
130                 bluetooth.title = metadata.Title
131         }
132     }
133
134     Connections {
135         target: mediaplayer
136
137         onPlaylistChanged: {
138             playlist_model.clear();
139
140             for (var i = 0; i < playlist.list.length; i++) {
141                 var item = playlist.list[i]
142
143                 playlist_model.append({ "index": item.index, "artist": item.artist ? item.artist : '', "title": item.title ? item.title : '' })
144
145                 if (item.selected) {
146                     playlistview.currentIndex = i
147                 }
148             }
149         }
150
151         onMetadataChanged: {
152             player.title = metadata.title
153             player.album = metadata.album
154             player.artist = metadata.artist
155
156             if (metadata.duration) {
157                 player.duration = metadata.duration
158             }
159
160             if (metadata.position) {
161                 player.position = metadata.position
162             }
163
164             if (metadata.status) {
165                 player.status = metadata.status
166             }
167
168             if (metadata.image) {
169                 player.cover_art = metadata.image
170             }
171
172             playlistview.currentIndex = metadata.index
173         }
174     }
175
176     Timer {
177         id: timer
178         interval: 250
179         running: (bluetooth.av_connected && bluetooth.state == "playing")
180         repeat: true
181         onTriggered: {
182             bluetooth.position = bluetooth.position + 250
183         }
184     }
185
186     ListModel {
187         id: playlist_model
188     }
189
190     ColumnLayout {
191         anchors.fill: parent
192         Item {
193             Layout.fillWidth: true
194             Layout.fillHeight: true
195             Layout.preferredHeight: 1080
196             clip: true
197             Image {
198                 id: albumart
199                 anchors.left: parent.left
200                 anchors.right: parent.right
201                 anchors.bottom: parent.bottom
202                 height: sourceSize.height * width / sourceSize.width
203                 fillMode: Image.PreserveAspectCrop
204                 source: player.cover_art ? player.cover_art : ''
205                 visible: bluetooth.av_connected == false
206             }
207
208             Item {
209                 anchors.left: parent.left
210                 anchors.right: parent.right
211                 anchors.bottom: parent.bottom
212                 height :307
213                 Rectangle {
214                     anchors.fill: parent
215                     color: 'black'
216                     opacity: 0.75
217                 }
218
219                 ColumnLayout {
220                     anchors.fill: parent
221                     anchors.margins: root.width * 0.02
222                     Item {
223                         Layout.fillWidth: true
224                         Layout.fillHeight: true
225                         Row {
226                             spacing: 20
227                             //ToggleButton {
228                             //    id: random
229                             //    visible: bluetooth.connected == false
230                             //    offImage: './images/AGL_MediaPlayer_Shuffle_Inactive.svg'
231                             //    onImage: './images/AGL_MediaPlayer_Shuffle_Active.svg'
232                             //}
233                             ToggleButton {
234                                 id: loop
235                                 visible: bluetooth.connected == false
236                                 //checked: player.loop_state
237                                 offImage: './images/AGL_MediaPlayer_Loop_Inactive.svg'
238                                 onImage: './images/AGL_MediaPlayer_Loop_Active.svg'
239                                 onClicked: { mediaplayer.loop(checked) }
240                             }
241                         }
242                         ColumnLayout {
243                             anchors.fill: parent
244                             Label {
245                                 id: title
246                                 Layout.alignment: Layout.Center
247                                 text: bluetooth.av_connected ? bluetooth.title : (player.title ? player.title : '')
248                                 horizontalAlignment: Label.AlignHCenter
249                                 verticalAlignment: Label.AlignVCenter
250                             }
251                             Label {
252                                 Layout.alignment: Layout.Center
253                                 text: bluetooth.av_connected ? bluetooth.artist : (player.artist ? player.artist : '')
254                                 horizontalAlignment: Label.AlignHCenter
255                                 verticalAlignment: Label.AlignVCenter
256                                 font.pixelSize: title.font.pixelSize * 0.6
257                             }
258                         }
259                     }
260                     Slider {
261                         id: slider
262                         Layout.fillWidth: true
263                         to: bluetooth.av_connected ? bluetooth.duration : player.duration
264                         enabled: bluetooth.av_connected == false
265                         value: bluetooth.av_connected ? bluetooth.position : player.position
266                         function getPosition() {
267                             if (bluetooth.av_connected) {
268                                 return player.time2str(bluetooth.position)
269                             }
270                             return player.time2str(player.position)
271                         }
272                         Label {
273                             id: position
274                             anchors.left: parent.left
275                             anchors.bottom: parent.top
276                             font.pixelSize: 32
277                             text: slider.getPosition()
278                         }
279                         Label {
280                             id: duration
281                             anchors.right: parent.right
282                             anchors.bottom: parent.top
283                             font.pixelSize: 32
284                             text: bluetooth.av_connected ? player.time2str(bluetooth.duration) : player.time2str(player.duration)
285                         }
286                         onPressedChanged: mediaplayer.seek(value)
287                     }
288                     RowLayout {
289                         Layout.fillHeight: true
290 //                        Image {
291 //                            source: './images/AGL_MediaPlayer_Playlist_Inactive.svg'
292 //                        }
293 //                        Image {
294 //                            source: './images/AGL_MediaPlayer_CD_Inactive.svg'
295 //                        }
296                         Item { Layout.fillWidth: true }
297                         ImageButton {
298                             id: previous
299                             offImage: './images/AGL_MediaPlayer_BackArrow.svg'
300                             onClicked: {
301                                 if (bluetooth.av_connected) {
302                                     bluetooth.set_avrcp_controls("Previous")
303                                     bluetooth.position = 0
304                                 } else {
305                                     mediaplayer.previous()
306                                 }
307                             }
308                         }
309                         ImageButton {
310                             id: play
311                             states: [
312                                 State {
313                                     when: !bluetooth.av_connected && player.status == "playing"
314                                     PropertyChanges {
315                                         target: play
316                                         offImage: './images/AGL_MediaPlayer_Player_Pause.svg'
317                                         onClicked: {
318                                             player.status = "stopped"
319                                             mediaplayer.pause()
320                                         }
321                                     }
322                                 },
323                                 State {
324                                     when: bluetooth.av_connected && bluetooth.state == "playing"
325                                     PropertyChanges {
326                                         target: play
327                                         offImage: './images/AGL_MediaPlayer_Player_Pause.svg'
328                                         onClicked: bluetooth.set_avrcp_controls("Pause")
329                                     }
330                                 },
331                                 State {
332                                     when: !bluetooth.av_connected && player.status != "playing"
333                                     PropertyChanges {
334                                         target: play
335                                         offImage: './images/AGL_MediaPlayer_Player_Play.svg'
336                                         onClicked: mediaplayer.play()
337                                     }
338                                 },
339                                 State {
340                                     when: bluetooth.av_connected && bluetooth.state != "playing"
341                                     PropertyChanges {
342                                         target: play
343                                         offImage: './images/AGL_MediaPlayer_Player_Play.svg'
344                                         onClicked: bluetooth.set_avrcp_controls("Play")
345                                     }
346                                 }
347                             ]
348                         }
349                         ImageButton {
350                             id: forward
351                             offImage: './images/AGL_MediaPlayer_ForwardArrow.svg'
352                             onClicked: {
353                                 if (bluetooth.av_connected) {
354                                     bluetooth.set_avrcp_controls("Next")
355                                 } else {
356                                     mediaplayer.next()
357                                 }
358                             }
359                         }
360
361                         Item { Layout.fillWidth: true }
362  
363                         ToggleButton {
364                               visible: bluetooth.connected
365                               checked: bluetooth.av_connected
366                               offImage: './images/AGL_MediaPlayer_Bluetooth_Inactive.svg'
367                               onImage: './images/AGL_MediaPlayer_Bluetooth_Active.svg'
368
369                               onClicked: {
370                                   if (bluetooth.av_connected) {
371                                       bluetooth.disconnect_profiles()
372                                   } else {
373                                       bluetooth.connect_profiles()
374                                   }
375                               }
376                         }
377                     }
378                 }
379             }
380         }
381         Item {
382             Layout.fillWidth: true
383             Layout.fillHeight: true
384             Layout.preferredHeight: 407
385
386             ListView {
387                 anchors.fill: parent
388                 id: playlistview
389                 visible: bluetooth.av_connected == false
390                 clip: true
391                 header: Label {
392                     x: 50
393                     text: 'PLAYLIST'
394                     opacity: 0.5
395                 }
396                 model: playlist_model
397                 currentIndex: -1
398
399                 delegate: MouseArea {
400                     id: delegate
401                     width: ListView.view.width
402                     height: ListView.view.height / 4
403                     RowLayout {
404                         anchors.fill: parent
405                         anchors.leftMargin: 50
406                         anchors.rightMargin: 50
407                         ColumnLayout {
408                             Layout.fillWidth: true
409                             Label {
410                                 Layout.fillWidth: true
411                                 text: model.title
412                             }
413                             Label {
414                                 Layout.fillWidth: true
415                                 text: model.artist
416                                 color: '#00ADDC'
417                                 font.pixelSize: 32
418                             }
419                         }
420                         //Label {
421                         //    text: player.time2str(model.duration)
422                         //    color: '#00ADDC'
423                         //    font.pixelSize: 32
424                         //}
425                     }
426                     onClicked: {
427                         mediaplayer.picktrack(playlistview.model.get(index).index)
428                     }
429                 }
430
431                 highlight: Rectangle {
432                     color: 'white'
433                     opacity: 0.25
434                 }
435             }
436         }
437     }
438 }