Guest User

AudioPlayer.qml

a guest
Jun 19th, 2014
338
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // -*- qml -*-
  2.  
  3. import QtQuick 2.0
  4. import Sailfish.Silica 1.0
  5. import Sailfish.Silica.theme 1.0
  6. import Sailfish.Media 1.0
  7. import com.jolla.mediaplayer 1.0
  8. import org.nemomobile.policy 1.0
  9.  
  10. DockedPanel {
  11. id: player
  12.  
  13. property bool active: audio.model.count > 0
  14. property bool showAddToPlaylistButton: true
  15.  
  16. property bool _grabKeys: active && keysResource.acquired
  17.  
  18. property alias currentItem: audio.currentItem
  19. property alias duration: audio.duration
  20. property int position: audio.position + _seekOffset
  21. property alias state: audio.state
  22. property alias playModel: audio.model
  23. readonly property bool playing: audio.state == Audio.Playing || _resume
  24. readonly property bool seeking: forwardKey.pressed || rewindKey.pressed
  25. property bool _resume
  26. property int _seekOffset
  27.  
  28. function seekForward(time) {
  29. player._seekOffset += time
  30. if (player.position > audio.duration) {
  31. player._seekOffset = audio.duration - audio.position
  32. }
  33. // Wired headsets can overload the fast forward key to mean next if held, but
  34. // bluetooth headsets will manage this themselves, and will auto repeat the key if held.
  35. // To support the wired headset we restart a timer on each key press and cancel it on
  36. // release, triggering the next song action on the timer expiring. If the key auto
  37. // repeats the restart will prevent the timer expiring and holding will act as a
  38. // series of successive presses.
  39. nextTimer.restart()
  40. }
  41.  
  42. function seekBackward(time) {
  43. player._seekOffset -= time
  44. if (player.position < 0) {
  45. player._seekOffset = -audio.position
  46. }
  47. previousTimer.restart()
  48. }
  49.  
  50. width: parent.width
  51.  
  52. height: column.height + 2*Theme.paddingLarge
  53. contentHeight: height
  54. flickableDirection: Flickable.VerticalFlick
  55.  
  56. visible: active && root.applicationActive
  57.  
  58. opacity: Qt.inputMethod.visible ? 0.0 : 1.0
  59. Behavior on opacity { FadeAnimation {}}
  60.  
  61.  
  62. function playIndex(index) {
  63. if (!audio.model) {
  64. return
  65. }
  66.  
  67. audio.model.currentIndex = index
  68. _play()
  69. }
  70.  
  71. function play(model, index) {
  72. audio.setPlayModel(model)
  73. audio.model.currentIndex = audio.model.shuffledIndex(index)
  74.  
  75. _play()
  76. }
  77.  
  78. function shuffleAndPlay(model, modelSize) {
  79. audio.setPlayModel(model)
  80.  
  81. audio.model.currentIndex = Math.floor(Math.random() * modelSize)
  82. audio.model.shuffle()
  83. _play()
  84. }
  85.  
  86. function addToQueue(mediaOrModel) {
  87. audio.addToQueue(mediaOrModel)
  88. }
  89.  
  90. function removeFromQueue(index) {
  91. var isPlaying = audio.state == Audio.Playing
  92. var isVisible = player.visible
  93.  
  94. if (index >= audio.model.count || index < 0) {
  95. console.log("Invalid index passed to removeFromQueue()")
  96. return
  97. }
  98.  
  99. // If it's the current item then we try to play the next one:
  100. if (index == audio.model.currentIndex) {
  101. audio.playNext()
  102.  
  103. if (!isPlaying) {
  104. player.stop()
  105. }
  106. }
  107.  
  108. // If it's still the currentIndex then we just stop playback.
  109. if (index == audio.model.currentIndex) {
  110. audio.model.currentIndex = -1
  111. }
  112.  
  113. audio.removeFromQueue(index)
  114. }
  115.  
  116. function removeItemFromQueue(mediaItem)
  117. {
  118. for (var i = audio.indexOf(mediaItem, 0); i != -1; i = audio.indexOf(mediaItem, i)) {
  119. removeFromQueue(i)
  120. }
  121. }
  122.  
  123. function playUrl(url) {
  124. audio.model.clear()
  125. audio.model.appendUrl(url)
  126. playIndex(0)
  127. }
  128. function toggleOrNext(){
  129. toggleTimer.start()
  130. toggle()
  131. }
  132.  
  133. function toggle() {
  134. if (playing) {
  135. pause()
  136. } else {
  137. _play()
  138. }
  139. }
  140.  
  141. function _play() {
  142. if (seeking) {
  143. _resume = true
  144. } else {
  145. audio.play()
  146. }
  147. showControls()
  148. }
  149.  
  150. function pause() {
  151. _resume = false
  152. audio.pause()
  153. }
  154.  
  155. function playNext() {
  156. audio.playNext()
  157. showControls()
  158. }
  159.  
  160. function showControls() {
  161. if (playing) {
  162. open = true
  163. }
  164. }
  165.  
  166. function hideControls() {
  167. open = false
  168. }
  169.  
  170. onOpenChanged: {
  171. if (!open) {
  172. audio.pause()
  173. }
  174. }
  175.  
  176. onSeekingChanged: {
  177. if (seeking) {
  178. _resume = audio.state == Audio.Playing
  179. audio.pause()
  180. } else {
  181. audio.position += _seekOffset
  182. _seekOffset = 0
  183. if (_resume) {
  184. _resume = false
  185. audio.play()
  186. }
  187. }
  188. }
  189.  
  190. onPositionChanged: if (!slider.pressed) slider.value = position / 1000
  191.  
  192. MediaKey { enabled: player._grabKeys; key: Qt.Key_MediaTogglePlayPause; onPressed: player.toggle() }
  193. MediaKey {
  194. enabled: player._grabKeys; key: Qt.Key_MediaPlay; onPressed: player._play()
  195. }
  196. MediaKey { enabled: player._grabKeys; key: Qt.Key_MediaPause; onPressed: player.pause()}
  197. MediaKey { enabled: player._grabKeys; key: Qt.Key_MediaStop; onPressed: audio.stop() }
  198. MediaKey { enabled: player._grabKeys; key: Qt.Key_MediaNext; onPressed: audio.playNext() }
  199. MediaKey { enabled: player._grabKeys; key: Qt.Key_MediaPrevious; onPressed: audio.playPrevious() }
  200. MediaKey { enabled: player._grabKeys; key: Qt.Key_ToggleCallHangup; onPressed: player.toggleOrNext(); onReleased: toggleTimer.stop() }
  201. Timer { id: toggleTimer; interval: 500; onTriggered: audio.playNext()}
  202. MediaKey {
  203. id: forwardKey
  204.  
  205. enabled: player._grabKeys
  206. key: Qt.Key_AudioForward
  207. onPressed: player.seekForward(5000)
  208. onRepeat: player.seekForward(1000)
  209. onReleased: nextTimer.stop()
  210. }
  211. Timer { id: nextTimer; interval: 500; onTriggered: audio.playNext() }
  212.  
  213. MediaKey {
  214. id: rewindKey
  215.  
  216. enabled: player._grabKeys
  217. key: Qt.Key_AudioRewind
  218. onPressed: player.seekBackward(5000)
  219. onRepeat: player.seekBackward(1000)
  220. onReleased: previousTimer.stop()
  221. }
  222. Timer { id: previousTimer; interval: 500; onTriggered: audio.playPrevious() }
  223.  
  224. Permissions {
  225. enabled: player.active
  226. applicationClass: "player"
  227.  
  228. Resource {
  229. id: keysResource
  230. type: Resource.HeadsetButtons
  231. optional: true
  232. }
  233. }
  234.  
  235. Audio {
  236. id: audio
  237. onEndOfMedia: audio.playNext()
  238. model.currentIndex: audio.model.currentIndex
  239. model.onShuffledChanged: shuffleSwitch.checked = model.shuffled
  240.  
  241. model.onCurrentIndexChanged: {
  242. if (model.currentIndex == -1) {
  243. audio.model.currentIndex = -1
  244. }
  245. }
  246.  
  247. onCurrentItemChanged: {
  248. player._seekOffset = 0
  249.  
  250. var metadata
  251. if (currentItem) {
  252. metadata = {
  253. 'title' : currentItem.title,
  254. 'artist' : currentItem.author,
  255. 'album' : currentItem.album,
  256. 'genre' : "",
  257. 'track' : model.currentIndex,
  258. 'trackCount': model.count,
  259. 'duration' : currentItem.duration * 1000
  260. }
  261. }
  262. bluetoothMediaPlayer.metadata = metadata
  263. }
  264.  
  265. onStateChanged: {
  266. if (state == Audio.Stopped) {
  267. player._resume = false
  268. }
  269. }
  270. function playNext() {
  271. var index = model.currentIndex + 1
  272. if (index >= model.count) {
  273. if (!repeatSwitch.checked) {
  274. return
  275. }
  276. index = 0
  277. }
  278.  
  279. model.currentIndex = index
  280. play()
  281. }
  282.  
  283. function playPrevious() {
  284. var position = audio.position
  285.  
  286. // We play previous if less than 5 seconds have elapsed.
  287. // otherwise we rewind the playing song
  288. if (position >= 5000) {
  289. audio.position = 0
  290. return
  291. }
  292.  
  293. var index = model.currentIndex - 1
  294. if (index < 0) {
  295. return
  296. }
  297.  
  298. model.currentIndex = index
  299. play()
  300. }
  301.  
  302. function setShuffle(shuffle) {
  303. model.shuffled = shuffle
  304. }
  305. }
  306. BluetoothMediaPlayer {
  307. id: bluetoothMediaPlayer
  308.  
  309. status: {
  310. if (audio.state == Audio.Playing) {
  311. return BluetoothMediaPlayer.Playing
  312. } else if (audio.state == Audio.Stopped) {
  313. return BluetoothMediaPlayer.Stopped
  314. } else if (rewindKey.pressed) {
  315. return BluetoothMediaPlayer.ReverseSeek
  316. } else if (forwardKey.pressed) {
  317. return BluetoothMediaPlayer.ForwardSeek
  318. } else {
  319. return BluetoothMediaPlayer.Paused
  320. }
  321. }
  322.  
  323. repeat: repeatSwitch.checked
  324. ? BluetoothMediaPlayer.RepeatAllTracks
  325. : BluetoothMediaPlayer.RepeatOff
  326.  
  327. shuffle: shuffleSwitch.checked
  328. ? BluetoothMediaPlayer.ShuffleAllTracks
  329. : BluetoothMediaPlayer.ShuffleOff
  330.  
  331. position: audio.position
  332.  
  333. onChangeRepeat: {
  334. if (repeat == BluetoothMediaPlayer.RepeatOff) {
  335. repeatSwitch.checked = false
  336. } else if (repeat == BluetoothMediaPlayer.RepeatAllTracks) {
  337. repeatSwitch.checked = true
  338. }
  339. }
  340.  
  341. onChangeShuffle: {
  342. if (shuffle == BluetoothMediaPlayer.ShuffleOff) {
  343. shuffleSwitch.checked = false
  344. } else if (shuffle == BluetoothMediaPlayer.ShuffleAllTracks) {
  345. shuffleSwitch.checked = true
  346. }
  347. }
  348. }
  349.  
  350. Column {
  351. id: column
  352.  
  353. width: parent.width
  354. y: Theme.paddingMedium
  355. spacing: Theme.paddingLarge
  356.  
  357. Slider {
  358. id: slider
  359.  
  360. property string author: currentItem ? currentItem.author : ""
  361. property string title: currentItem ? currentItem.title : ""
  362.  
  363. width: parent.width
  364. handleVisible: false
  365. valueText: Format.formatDuration(slider.value, slider.value >= 3600
  366. ? Format.DurationLong
  367. : Format.DurationShort)
  368.  
  369. label: {
  370. if (author.length > 0) {
  371. if (title.length > 0) {
  372. return "%0 - %1".arg(author).arg(title)
  373. } else {
  374. return author
  375. }
  376. } else if (title.length > 0) {
  377. return title
  378. }
  379. return ""
  380. }
  381.  
  382. minimumValue: 0
  383. maximumValue: audio.duration / 1000
  384. onReleased: audio.position = value * 1000
  385. }
  386. Row {
  387. id: navigation
  388. width: parent.width
  389.  
  390. IconButton {
  391. id: gotoPrevious
  392. width: parent.width / 3
  393. icon.source: "image://theme/icon-m-previous"
  394. anchors.verticalCenter: parent.verticalCenter
  395. onClicked: audio.playPrevious()
  396. }
  397.  
  398. IconButton {
  399. id: playPause
  400. width: parent.width / 3
  401. icon.source: audio.state == Audio.Playing ? "image://theme/icon-m-pause?" + Theme.highlightColor : "image://theme/icon-m-play"
  402. onClicked: audio.toggle()
  403. }
  404.  
  405. IconButton {
  406. id: gotoNext
  407. width: parent.width / 3
  408. icon.source: "image://theme/icon-m-next"
  409. anchors.verticalCenter: parent.verticalCenter
  410. onClicked: audio.playNext()
  411. }
  412. }
  413. }
  414.  
  415. PushUpMenu {
  416. id: bottomMenu
  417.  
  418. Row {
  419. id: row
  420. width: parent.width
  421.  
  422. property real childWidth: width / (showAddToPlaylistButton ? 3 : 2)
  423.  
  424. Behavior on childWidth {
  425. NumberAnimation { duration: 250 }
  426. }
  427.  
  428. IconButton {
  429. id: addToPlaylistButton
  430. width: row.childWidth
  431. anchors.bottom: parent.bottom
  432. anchors.bottomMargin: -Theme.paddingSmall
  433. icon.source: "image://theme/icon-m-add"
  434. opacity: showAddToPlaylistButton ? 1.0 : 0
  435. visible: opacity != 0
  436.  
  437. Behavior on opacity { FadeAnimation {} }
  438.  
  439. onClicked: {
  440. bottomMenu.hide()
  441. pageStack.push(Qt.resolvedUrl("AddToPlaylistPage.qml"), {media: audio.currentItem})
  442. }
  443. }
  444.  
  445. /*
  446. TODO: Implement sharing
  447. IconButton {
  448. width: row.childWidth
  449. anchors.bottom: parent.bottom
  450. icon.source: "image://theme/icon-l-share"
  451. }
  452. */
  453.  
  454. Switch {
  455. id: shuffleSwitch
  456. width: row.childWidth
  457. anchors.bottom: parent.bottom
  458. icon.source: "image://theme/icon-m-shuffle"
  459. onCheckedChanged: audio.setShuffle(checked)
  460. }
  461.  
  462. Switch {
  463. id: repeatSwitch
  464. width: row.childWidth
  465. anchors.bottom: parent.bottom
  466. icon.source: "image://theme/icon-m-repeat"
  467. }
  468. }
  469. }
  470. }
RAW Paste Data