Status: | Merged |
---|---|
Approved by: | Michael Sheldon |
Approved revision: | no longer in the source branch. |
Merged at revision: | 210 |
Proposed branch: | lp:~dano6/podbird/podbird |
Merge into: | lp:podbird |
Diff against target: |
273 lines (+84/-38) 6 files modified
app/podbird.qml (+31/-26) app/podcasts.js (+4/-0) app/ui/EpisodesPage.qml (+2/-2) app/ui/EpisodesTab.qml (+5/-5) app/ui/FullPlayingView.qml (+28/-5) app/ui/SettingsPage.qml (+14/-0) |
To merge this branch: | bzr merge lp:~dano6/podbird/podbird |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Sheldon | Approve | ||
Daniel Kutka (community) | Needs Resubmitting | ||
Review via email: mp+387072@code.launchpad.net |
Commit message
Implemented restoring queue, resuming of episode where stopped and fixed some playback issues
Description of the change
This MR brings restoring queue after restart and restoring playback position (optional). Seeking slider behavior was ported from music-app, that fixes some playback issues - sluggish seeking, getting weird loop after seeking, brings overall better responsiveness of controlling audio.
To post a comment you must log in.
Revision history for this message
Daniel Kutka (dano6) : | # |
review:
Needs Resubmitting
Revision history for this message
Michael Sheldon (michael-sheldon) wrote : | # |
Excellent, thanks very much!
review:
Approve
lp:~dano6/podbird/podbird
updated
- 210. By Michael Sheldon
-
Merge lp:~dano6/podbird/podbird:
* Restore queue after restart
* Fix resuming playback
* Fix some playback/seeking issues
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'app/podbird.qml' | |||
2 | --- app/podbird.qml 2020-06-30 11:10:05 +0000 | |||
3 | +++ app/podbird.qml 2020-07-09 11:20:38 +0000 | |||
4 | @@ -42,13 +42,6 @@ | |||
5 | 42 | theme.name: settings.themeName == "Dark.qml" ? "Ubuntu.Components.Themes.SuruDark" | 42 | theme.name: settings.themeName == "Dark.qml" ? "Ubuntu.Components.Themes.SuruDark" |
6 | 43 | : "Ubuntu.Components.Themes.Ambiance" | 43 | : "Ubuntu.Components.Themes.Ambiance" |
7 | 44 | 44 | ||
8 | 45 | Component.onDestruction: { | ||
9 | 46 | var db = Podcasts.init() | ||
10 | 47 | db.transaction(function (tx) { | ||
11 | 48 | tx.executeSql('UPDATE Episode SET queued=0 WHERE queued=1'); | ||
12 | 49 | }) | ||
13 | 50 | Podcasts.clearQueue() | ||
14 | 51 | } | ||
15 | 52 | 45 | ||
16 | 53 | // RefreshModel function to call refreshModel() function of the tab currently | 46 | // RefreshModel function to call refreshModel() function of the tab currently |
17 | 54 | // visible on application start. | 47 | // visible on application start. |
18 | @@ -62,9 +55,6 @@ | |||
19 | 62 | 55 | ||
20 | 63 | Component.onCompleted: { | 56 | Component.onCompleted: { |
21 | 64 | var db = Podcasts.init() | 57 | var db = Podcasts.init() |
22 | 65 | db.transaction(function (tx) { | ||
23 | 66 | tx.executeSql('UPDATE Episode SET queued=0 WHERE queued=1'); | ||
24 | 67 | }) | ||
25 | 68 | 58 | ||
26 | 69 | var today = new Date() | 59 | var today = new Date() |
27 | 70 | // Only automatically check for podcasts on launch once every 12 hours | 60 | // Only automatically check for podcasts on launch once every 12 hours |
28 | @@ -126,6 +116,8 @@ | |||
29 | 126 | property bool showListView: true | 116 | property bool showListView: true |
30 | 127 | property int skipForward: 30 | 117 | property int skipForward: 30 |
31 | 128 | property int skipBack: 10 | 118 | property int skipBack: 10 |
32 | 119 | property bool continueWhereStopped: true | ||
33 | 120 | property int playlistIndex: -1 | ||
34 | 129 | property bool downloadOverWifiOnly: true | 121 | property bool downloadOverWifiOnly: true |
35 | 130 | } | 122 | } |
36 | 131 | 123 | ||
37 | @@ -232,14 +224,15 @@ | |||
38 | 232 | // but media-hub doesn't report it correctly | 224 | // but media-hub doesn't report it correctly |
39 | 233 | console.log("Starting playback") | 225 | console.log("Starting playback") |
40 | 234 | player.play() | 226 | player.play() |
41 | 235 | player.restorePosition() | ||
42 | 236 | } | 227 | } |
43 | 237 | } | 228 | } |
44 | 238 | 229 | ||
45 | 239 | MediaPlayer { | 230 | MediaPlayer { |
46 | 240 | id: player | 231 | id: player |
47 | 241 | 232 | ||
49 | 242 | onPositionChanged: console.log("DEBUG: player position changed to: ", position) | 233 | onPositionChanged: { |
50 | 234 | restorePosition() | ||
51 | 235 | } | ||
52 | 243 | 236 | ||
53 | 244 | // Wrapper function around decodeURIComponent() to prevent exceptions | 237 | // Wrapper function around decodeURIComponent() to prevent exceptions |
54 | 245 | // from bubbling up to the app. | 238 | // from bubbling up to the app. |
55 | @@ -277,26 +270,41 @@ | |||
56 | 277 | savePosition() | 270 | savePosition() |
57 | 278 | } else { | 271 | } else { |
58 | 279 | play() | 272 | play() |
59 | 280 | // Clear the last saved position when we start playing again | ||
60 | 281 | clearPosition() | ||
61 | 282 | } | 273 | } |
62 | 283 | } | 274 | } |
63 | 284 | 275 | ||
64 | 285 | function savePosition() { | 276 | function savePosition() { |
65 | 277 | podbird.settings.playlistIndex = playlist.currentIndex | ||
66 | 286 | if (currentGuid) { | 278 | if (currentGuid) { |
67 | 287 | var db = Podcasts.init() | 279 | var db = Podcasts.init() |
68 | 288 | db.transaction(function (tx) { | 280 | db.transaction(function (tx) { |
69 | 289 | tx.executeSql("UPDATE Episode SET position=? WHERE guid=?", [player.position, currentGuid]) | 281 | tx.executeSql("UPDATE Episode SET position=? WHERE guid=?", [player.position, currentGuid]) |
70 | 290 | tx.executeSql("UPDATE Queue SET position=? WHERE guid=?", [player.position, currentGuid]) | 282 | tx.executeSql("UPDATE Queue SET position=? WHERE guid=?", [player.position, currentGuid]) |
71 | 283 | if(player.position / player.duration > 0.90) | ||
72 | 284 | tx.executeSql("UPDATE Episode SET listened=1 WHERE guid=?", [currentGuid]) | ||
73 | 291 | }) | 285 | }) |
74 | 292 | } | 286 | } |
75 | 293 | } | 287 | } |
76 | 294 | 288 | ||
77 | 289 | function restoreFromQueue() { | ||
78 | 290 | var db = Podcasts.init() | ||
79 | 291 | db.transaction(function (tx) { | ||
80 | 292 | var rs = tx.executeSql("SELECT * FROM Queue") | ||
81 | 293 | for (var i=0; i<rs.rows.length;i++) { | ||
82 | 294 | var episode = rs.rows.item(i) | ||
83 | 295 | player.playlist.addItem(episode.url) | ||
84 | 296 | } | ||
85 | 297 | }) | ||
86 | 298 | if(playlist.itemCount > podbird.settings.playlistIndex) | ||
87 | 299 | playlist.currentIndex = podbird.settings.playlistIndex | ||
88 | 300 | } | ||
89 | 301 | |||
90 | 295 | function restorePosition() { | 302 | function restorePosition() { |
95 | 296 | if (playbackState === MediaPlayer.PlayingState && pendingSeek !== 0) { | 303 | if(playbackState === MediaPlayer.PlayingState && status === MediaPlayer.Loaded && pendingSeek){ |
96 | 297 | player.seek(pendingSeek) | 304 | //ugly hack because seek function does not seems to work async |
97 | 298 | player.clearPosition() | 305 | var p = pendingSeek |
98 | 299 | pendingSeek = 0; | 306 | pendingSeek = 0 |
99 | 307 | player.seek(p) | ||
100 | 300 | } | 308 | } |
101 | 301 | } | 309 | } |
102 | 302 | 310 | ||
103 | @@ -345,7 +353,6 @@ | |||
104 | 345 | } | 353 | } |
105 | 346 | 354 | ||
106 | 347 | property bool endOfMedia: false | 355 | property bool endOfMedia: false |
107 | 348 | property double progress: 0 | ||
108 | 349 | property int pendingSeek: 0 | 356 | property int pendingSeek: 0 |
109 | 350 | 357 | ||
110 | 351 | playlist: Playlist { | 358 | playlist: Playlist { |
111 | @@ -361,16 +368,10 @@ | |||
112 | 361 | currentArtist = meta.artist | 368 | currentArtist = meta.artist |
113 | 362 | currentImage = meta.image | 369 | currentImage = meta.image |
114 | 363 | currentGuid = meta.guid | 370 | currentGuid = meta.guid |
117 | 364 | player.pendingSeek = meta.position | 371 | player.pendingSeek = podbird.settings.continueWhereStopped && meta.position > 5000 ? meta.position : 0 |
116 | 365 | console.log("DEBUG: player.pendingSeek: ", player.pendingSeek, " meta.position ", meta.position) | ||
118 | 366 | } | 372 | } |
119 | 367 | } | 373 | } |
120 | 368 | 374 | ||
121 | 369 | onPlaybackStateChanged: { | ||
122 | 370 | console.log("DEBUG: Restoring Position to: ", player.position) | ||
123 | 371 | restorePosition() | ||
124 | 372 | } | ||
125 | 373 | |||
126 | 374 | onStatusChanged: { | 375 | onStatusChanged: { |
127 | 375 | if (status === MediaPlayer.EndOfMedia) { | 376 | if (status === MediaPlayer.EndOfMedia) { |
128 | 376 | console.log("[LOG]: End of Media. Stopping.") | 377 | console.log("[LOG]: End of Media. Stopping.") |
129 | @@ -399,6 +400,10 @@ | |||
130 | 399 | Component.onDestruction: { | 400 | Component.onDestruction: { |
131 | 400 | savePosition() | 401 | savePosition() |
132 | 401 | } | 402 | } |
133 | 403 | |||
134 | 404 | Component.onCompleted: { | ||
135 | 405 | restoreFromQueue() | ||
136 | 406 | } | ||
137 | 402 | } | 407 | } |
138 | 403 | 408 | ||
139 | 404 | PageStack { | 409 | PageStack { |
140 | 405 | 410 | ||
141 | === modified file 'app/podcasts.js' | |||
142 | --- app/podcasts.js 2017-01-03 00:02:16 +0000 | |||
143 | +++ app/podcasts.js 2020-07-09 11:20:38 +0000 | |||
144 | @@ -104,6 +104,10 @@ | |||
145 | 104 | db.transaction(function(tx) { | 104 | db.transaction(function(tx) { |
146 | 105 | tx.executeSql("DELETE FROM Queue"); | 105 | tx.executeSql("DELETE FROM Queue"); |
147 | 106 | }); | 106 | }); |
148 | 107 | |||
149 | 108 | db.transaction(function (tx) { | ||
150 | 109 | tx.executeSql('UPDATE Episode SET queued=0 WHERE queued=1'); | ||
151 | 110 | }); | ||
152 | 107 | } | 111 | } |
153 | 108 | 112 | ||
154 | 109 | function lookup(source) { | 113 | function lookup(source) { |
155 | 110 | 114 | ||
156 | === modified file 'app/ui/EpisodesPage.qml' | |||
157 | --- app/ui/EpisodesPage.qml 2019-10-18 11:56:16 +0000 | |||
158 | +++ app/ui/EpisodesPage.qml 2020-07-09 11:20:38 +0000 | |||
159 | @@ -574,8 +574,8 @@ | |||
160 | 574 | 574 | ||
161 | 575 | subtitle.text: model.duration === 0 || model.duration === undefined ? model.downloadedfile ? "📎 " + Qt.formatDate(new Date(model.published), "MMM d, yyyy") | 575 | subtitle.text: model.duration === 0 || model.duration === undefined ? model.downloadedfile ? "📎 " + Qt.formatDate(new Date(model.published), "MMM d, yyyy") |
162 | 576 | : Qt.formatDate(new Date(model.published), "MMM d, yyyy") | 576 | : Qt.formatDate(new Date(model.published), "MMM d, yyyy") |
165 | 577 | : model.downloadedfile ? "📎 " + Podcasts.formatEpisodeTime(model.duration) + " | " + Qt.formatDate(new Date(model.published), "MMM d, yyyy") | 577 | : model.downloadedfile ? "📎 " + (model.position ? Podcasts.formatEpisodeTime(model.position/1000) + "/" : "") + Podcasts.formatEpisodeTime(model.duration) + " | " + Qt.formatDate(new Date(model.published), "MMM d, yyyy") |
166 | 578 | : Podcasts.formatEpisodeTime(model.duration) + " | " + Qt.formatDate(new Date(model.published), "MMM d, yyyy") | 578 | : (model.position ? Podcasts.formatEpisodeTime(model.position/1000) + "/" : "") + Podcasts.formatEpisodeTime(model.duration) + " | " + Qt.formatDate(new Date(model.published), "MMM d, yyyy") |
167 | 579 | subtitle.color: podbird.appTheme.baseSubText | 579 | subtitle.color: podbird.appTheme.baseSubText |
168 | 580 | 580 | ||
169 | 581 | padding.top: units.gu(1) | 581 | padding.top: units.gu(1) |
170 | 582 | 582 | ||
171 | === modified file 'app/ui/EpisodesTab.qml' | |||
172 | --- app/ui/EpisodesTab.qml 2019-03-26 10:39:53 +0000 | |||
173 | +++ app/ui/EpisodesTab.qml 2020-07-09 11:20:38 +0000 | |||
174 | @@ -544,8 +544,8 @@ | |||
175 | 544 | 544 | ||
176 | 545 | subtitle.text: model.duration === 0 || model.duration === undefined ? model.downloadedfile ? "📎 " + model.artist | 545 | subtitle.text: model.duration === 0 || model.duration === undefined ? model.downloadedfile ? "📎 " + model.artist |
177 | 546 | : model.artist | 546 | : model.artist |
180 | 547 | : model.downloadedfile ? "📎 " + Podcasts.formatEpisodeTime(model.duration) + " | " + model.artist | 547 | : model.downloadedfile ? "📎 " + (model.position ? Podcasts.formatEpisodeTime(model.position/1000) + "/" : "") + Podcasts.formatEpisodeTime(model.duration) + " | " + model.artist |
181 | 548 | : Podcasts.formatEpisodeTime(model.duration) + " | " + model.artist | 548 | : (model.position ? Podcasts.formatEpisodeTime(model.position/1000) + "/" : "") + Podcasts.formatEpisodeTime(model.duration) + " | " + model.artist |
182 | 549 | subtitle.color: podbird.appTheme.baseSubText | 549 | subtitle.color: podbird.appTheme.baseSubText |
183 | 550 | 550 | ||
184 | 551 | Image { | 551 | Image { |
185 | @@ -747,9 +747,9 @@ | |||
186 | 747 | episodesModel.append({"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "favourited": episode.favourited, "diff": "Older"}) | 747 | episodesModel.append({"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "favourited": episode.favourited, "diff": "Older"}) |
187 | 748 | } | 748 | } |
188 | 749 | } else if (diff >= 7) { | 749 | } else if (diff >= 7) { |
192 | 750 | if (episode.downloadedfile != null) { | 750 | if (episode.downloadedfile != null) { |
193 | 751 | episodesModel.append({"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "favourited": episode.favourited, "diff": "Older"}) | 751 | episodesModel.append({"guid" : episode.guid, "listened" : episode.listened, "published": episode.published, "name" : episode.name, "description" : episode.description, "duration" : episode.duration, "position" : episode.position, "downloadedfile" : episode.downloadedfile, "image" : podcast.image, "artist" : podcast.artist, "audiourl" : episode.audiourl, "queued": episode.queued, "favourited": episode.favourited, "diff": "Older"}) |
194 | 752 | } | 752 | } |
195 | 753 | } | 753 | } |
196 | 754 | } | 754 | } |
197 | 755 | 755 | ||
198 | 756 | 756 | ||
199 | === modified file 'app/ui/FullPlayingView.qml' | |||
200 | --- app/ui/FullPlayingView.qml 2017-01-01 14:28:51 +0000 | |||
201 | +++ app/ui/FullPlayingView.qml 2020-07-09 11:20:38 +0000 | |||
202 | @@ -133,10 +133,24 @@ | |||
203 | 133 | maximumValue: player.duration | 133 | maximumValue: player.duration |
204 | 134 | value: player.position | 134 | value: player.position |
205 | 135 | height: units.gu(2) | 135 | height: units.gu(2) |
210 | 136 | 136 | ||
211 | 137 | onValueChanged: { | 137 | property bool seeking: false |
212 | 138 | if (pressed) { | 138 | property bool seeked: false |
213 | 139 | player.seek(value); | 139 | |
214 | 140 | onSeekingChanged: { | ||
215 | 141 | if (seeking === false) { | ||
216 | 142 | startTime.text = Podcasts.formatTime(player.position / 1000) | ||
217 | 143 | } | ||
218 | 144 | } | ||
219 | 145 | |||
220 | 146 | onPressedChanged: { | ||
221 | 147 | seeking = pressed | ||
222 | 148 | |||
223 | 149 | if (!pressed) { | ||
224 | 150 | seeked = true | ||
225 | 151 | player.seek(value) | ||
226 | 152 | |||
227 | 153 | startTime.text = Podcasts.formatTime(value / 1000) | ||
228 | 140 | } | 154 | } |
229 | 141 | } | 155 | } |
230 | 142 | 156 | ||
231 | @@ -146,7 +160,16 @@ | |||
232 | 146 | 160 | ||
233 | 147 | Connections { | 161 | Connections { |
234 | 148 | target: player | 162 | target: player |
236 | 149 | onPositionChanged: scrubber.value = player.position | 163 | onPositionChanged: { |
237 | 164 | // seeked is a workaround for bug 1310706 as the first position after a seek is sometimes invalid (0) | ||
238 | 165 | if (scrubber.seeking === false && !scrubber.seeked) { | ||
239 | 166 | startTime.text = Podcasts.formatTime(player.position / 1000) | ||
240 | 167 | endTime.text = Podcasts.formatTime(player.duration / 1000) | ||
241 | 168 | |||
242 | 169 | scrubber.value = player.position | ||
243 | 170 | } | ||
244 | 171 | scrubber.seeked = false; | ||
245 | 172 | } | ||
246 | 150 | } | 173 | } |
247 | 151 | 174 | ||
248 | 152 | Label { | 175 | Label { |
249 | 153 | 176 | ||
250 | === modified file 'app/ui/SettingsPage.qml' | |||
251 | --- app/ui/SettingsPage.qml 2016-12-11 21:23:21 +0000 | |||
252 | +++ app/ui/SettingsPage.qml 2020-07-09 11:20:38 +0000 | |||
253 | @@ -180,6 +180,20 @@ | |||
254 | 180 | onClicked: PopupUtils.open(skipBackDialog, settingsPage); | 180 | onClicked: PopupUtils.open(skipBackDialog, settingsPage); |
255 | 181 | } | 181 | } |
256 | 182 | 182 | ||
257 | 183 | ListItem { | ||
258 | 184 | ListItemLayout { | ||
259 | 185 | id: continueWhereStopped | ||
260 | 186 | title.text: i18n.tr("Continue where stopped") | ||
261 | 187 | Switch { | ||
262 | 188 | SlotsLayout.position: SlotsLayout.Last | ||
263 | 189 | checked: podbird.settings.continueWhereStopped | ||
264 | 190 | onClicked: podbird.settings.continueWhereStopped = checked | ||
265 | 191 | } | ||
266 | 192 | } | ||
267 | 193 | divider.visible: false | ||
268 | 194 | height: gridViewLayout.height | ||
269 | 195 | } | ||
270 | 196 | |||
271 | 183 | HeaderListItem { | 197 | HeaderListItem { |
272 | 184 | title.text: i18n.tr("Podcast Episode Settings") | 198 | title.text: i18n.tr("Podcast Episode Settings") |
273 | 185 | } | 199 | } |
Overall this has some excellent improvements, thanks very much! There's a couple of minor things to address that I've added as diff comments