relistennet / relisten-ios Goto Github PK
View Code? Open in Web Editor NEWAn iOS app for streaming millions of free music recordings from the Internet Archive
Home Page: http://relisten.net
License: MIT License
An iOS app for streaming millions of free music recordings from the Internet Archive
Home Page: http://relisten.net
License: MIT License
I'm seeing track durations occasionally show up like '05:40' or '2:0'. The duration usually looks fine if I go back out of the show view and back in, so this is a race condition somewhere when formatting the string.
DateComponentsFormatter claims to be thread-safe but when I add a serial queue to the humanize()
function this bug goes away.
The serial queue is a good band aid for now, but I'd like to get to the bottom of why this race is happening, and I'll use this bug report to track that.
It should be wired up, and we should decide whether it's for making a track offline or favoriting the track. The icon should probably be changed to match what it does.
From the more actions menu from a track, we should have these options:
I added Bela Fleck and the Flecktones to my favorite artists and the app hung with this error and stack trace:
2018-08-10 16:48:53.973308-0700 Relisten[33346:8463063] [Collection] reloadData <ASCollectionNode: 0x7f94a15072a0>
2018-08-10 16:48:53.973675-0700 Relisten[33346:8463063] [Collection] New content: { itemCounts = [ <S0: 25> ] }
2018-08-10 16:48:53.989485-0700 Relisten[33346:8463063] The behavior of the UICollectionViewFlowLayout is not defined because:
2018-08-10 16:48:53.989619-0700 Relisten[33346:8463063] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
2018-08-10 16:48:53.989712-0700 Relisten[33346:8463063] Please check the values returned by the delegate.
2018-08-10 16:48:53.990370-0700 Relisten[33346:8463063] The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7f94a1508890>, and it is attached to <ASCollectionView: 0x7f94a1a49600; baseClass = UICollectionView; frame = (0 0; 375 120); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x60c0006530b0>; layer = <ASCollectionNode-Layer: 0x60c000642be0; node = <ASCollectionNode: 0x7f94a15072a0>>; contentOffset: {0, 0}; contentSize: {5263.9999999999991, 120}; adjustedContentInset: {0, 0, 0, 0}> collection view layout: <UICollectionViewFlowLayout: 0x7f94a1508890>.
2018-08-10 16:48:53.990464-0700 Relisten[33346:8463063] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x000000010962ad84 UIKit`UICollectionViewFlowLayoutBreakForInvalidSizes
frame #1: 0x000000010962e9de UIKit`-[_UIFlowLayoutSection logInvalidSizesForHorizontalDirection:warnAboutDelegateValues:] + 219
frame #2: 0x000000010962e8f0 UIKit`-[_UIFlowLayoutSection logInvalidSizes] + 72
frame #3: 0x0000000109630c34 UIKit`-[_UIFlowLayoutSection computeLayoutInRect:forSection:invalidating:invalidationContext:] + 7606
frame #4: 0x00000001095b1536 UIKit`__76-[UICollectionViewFlowLayout _updateItemsLayoutForRect:allowsPartialUpdate:]_block_invoke + 533
frame #5: 0x000000010bc3cb4e CoreFoundation`-[__NSArrayM enumerateObjectsWithOptions:usingBlock:] + 222
frame #6: 0x00000001095b119b UIKit`-[UICollectionViewFlowLayout _updateItemsLayoutForRect:allowsPartialUpdate:] + 495
frame #7: 0x00000001095a9b5c UIKit`-[UICollectionViewFlowLayout invalidateLayoutWithContext:] + 1331
frame #8: 0x000000010959ed90 UIKit`-[UICollectionViewLayout _invalidateLayoutUsingContext:] + 63
frame #9: 0x000000010955f495 UIKit`-[UICollectionView _invalidateLayoutIfNecessaryForReload] + 155
frame #10: 0x000000010955e717 UIKit`-[UICollectionView reloadData] + 985
frame #11: 0x0000000105a47f47 AsyncDisplayKit`::__64-[ASCollectionView rangeController:updateWithChangeSet:updates:]_block_invoke(.block_descriptor=0x00007ffeeacadc48) at ASCollectionView.mm:2131
frame #12: 0x0000000105a47e8b AsyncDisplayKit`ASPerformBlockWithoutAnimation(withoutAnimation=NO, block=0x0000000105a47ec0) block_pointer) at ASInternalHelpers.h:92
frame #13: 0x0000000105a47b63 AsyncDisplayKit`::-[ASCollectionView rangeController:updateWithChangeSet:updates:](self=0x00007f94a1a49600, _cmd="rangeController:updateWithChangeSet:updates:", rangeController=0x000060c0002ad4a0, changeSet=0x00006040005a6c80, updates=0x0000000105a61950) at ASCollectionView.mm:2126
frame #14: 0x0000000105b5562d AsyncDisplayKit`::-[ASRangeController dataController:updateWithChangeSet:updates:](self=0x000060c0002ad4a0, _cmd="dataController:updateWithChangeSet:updates:", dataController=0x000060c000137e80, changeSet=0x00006040005a6c80, updates=0x0000000105a61950) at ASRangeController.mm:521
frame #15: 0x0000000105a618d6 AsyncDisplayKit`::__40-[ASDataController updateWithChangeSet:]_block_invoke_2.224(.block_descriptor=0x000060800107ca00) at ASDataController.mm:610
frame #16: 0x0000000105b29d94 AsyncDisplayKit`::__30-[ASMainSerialQueue runBlocks]_block_invoke(.block_descriptor=0x00006080006527e0) at ASMainSerialQueue.mm:73
frame #17: 0x000000010dd407ab libdispatch.dylib`_dispatch_call_block_and_release + 12
frame #18: 0x000000010dd417ec libdispatch.dylib`_dispatch_client_callout + 8
frame #19: 0x000000010dd4c8cf libdispatch.dylib`_dispatch_main_queue_callback_4CF + 628
frame #20: 0x000000010bc72c99 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
frame #21: 0x000000010bc36ea6 CoreFoundation`__CFRunLoopRun + 2342
frame #22: 0x000000010bc3630b CoreFoundation`CFRunLoopRunSpecific + 635
frame #23: 0x000000010f573a73 GraphicsServices`GSEventRunModal + 62
frame #24: 0x0000000108a9f057 UIKit`UIApplicationMain + 159
frame #25: 0x0000000104f684c7 Relisten`main at RLAppDelegate.swift:20
frame #26: 0x000000010ddbe955 libdyld.dylib`start + 1
frame #27: 0x000000010ddbe955 libdyld.dylib`start + 1
I put my phone in airplane mode and Relisten crashes on launch.
I’m not near a desktop so I can’t get crash log symbols but this is probably pretty easy to reproduce.
The show trays on the artists view controller are frequently showing up with no cells inside of them.
https://github.com/Lickability/PinpointKit#usage
Can also detect when a screenshot is taken if needed. Will need to investigate how this plays with Crashlytics logging (#79).
email should be ios at relisten dot net
As it says in the title. Closes #9.
We need to eliminate Firebase and come up with a better way for users to manage things.
This is on the iOS repo for now even though it covers more products.
Release target: 2018-09-01. Beta release target: 2018-08-25.
Release target: 2018-10-01. Beta-release target: 2018-09-24.
Release target: 2018-XX-XX. Beta-release target: 2018-XX-XX.
If Relisten isn't running and I launch it from CarPlay the app crashes on launch. I'm pretty sure this is due to BASS not being ok with being initialized while the device is locked, but I can't get symbols out of this crash report.
This is simple enough to reproduce, but I'm going to have to sit in my parked car looking like a dork while debugging this.
I've noticed that when I use CarPlay and my network connection is slow I'll tap to load something and I'll get results for a different artist than I tapped on.
I'm pretty sure this is happening because the controller is getting an update from Siesta (or maybe Firebase favorites loading) and updating the artist array, which bumps the indices of everything, confusing CarPlay.
I'll need to add some locking around what's currently being displayed, and hold any updates until the user pops back up the navigation stack.
Tracked by alecgorge/gapless-audio-bass-ios#10
While watching the logs I noticed that my favorites are getting re-imported from SDCloudUserDefaults
every time the app launches. The import code is clearing out the favorites and saving them back to SDCloudUserDefaults
so I'm not sure why they keep coming back.
https://docs.fabric.io/apple/crashlytics/enhanced-reports.html#custom-logging
We should also update AGAudioPlayer/BASS gapless to support a custom logging callback function to track all of this.
Either (a) we need to show download percentage (not worth it imo) or (b) make it so the flashing happens at the same time. I think the easiest way is to try to time everything to start to the nearest .5 or .25 second using the delay part of UIAnimation. Low priority though
Most of the time when I launch Relisten I'm ready to listen to some music, but unless I'm currently in the middle of a show I don't quite know what I want to listen to.
I'd like to add a list of recommended shows on the home screen based on the user's listening history to make it easier to launch Relisten and jump right into listening to something new.
Right now, Relisten stores the 25 most recently played tracks, and it only counts a track as recently played if you pass the halfway mark of the track. This can be problematic if you start listening to a show and only make it a couple minutes in to a song and then get interrupted. It's also a bit confusing, since I would expect a track to show up as recently played as soon as I start listening to it.
Relisten should store the most recently played track immediately on playback start, but it should only store one recently played track per artist.
Relisten should keep a history of every track that has been listened to. Only marking as listened after the halfway point makes sense for considering a song completely listened to (like publishing to last.fm and phishtrackstats.
This history can potentially end up being a large number of small entries, so a sqlite database seems like the best fit for this. Relisten should be able to quickly query for things like:
If an favorited artist has performed a show within the last month, the most recent show should appear in the recommended shows list.
The rest of the discover list should be populated with:
For the purposes of these sections, favorite artists should be any artist the user has explicitly favorited, but it should also include any artist that the user seems to be listening to more than casually right now. If the user has listened to 20 or more complete tracks from an artist in the last month then they should be included in the favorite artists list for picking recommended shows.
The "Available Offline" section can be dropped from the main screen, since favorite or recently played shows are more important most of the time. However, there's one time when showing it is very helpful, and that's when the user is offline.
If Relisten detects that it can't reach the Relisten API servers then the My Stuff section should be filled with offline shows sorted in this order:
I hit this crash on launch just once. It looks like it's a race condition with reloading the data in the ArtistsViewController
2018-08-10 16:46:17.526039-0700 Relisten[33242:8458741] Invalid number of items in section 5. The number of items after the update (2) must be equal to the number of items before the update (0) plus or minus the number of items inserted or deleted (0 inserted, 0 deleted).
Backtrace:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000110be4001 libobjc.A.dylib`objc_exception_throw
frame #1: 0x00000001115c4975 CoreFoundation`+[NSException raise:format:] + 197
frame #2: 0x000000010b2feff2 AsyncDisplayKit`::-[ASDataController updateWithChangeSet:](self=0x0000604000127620, _cmd="updateWithChangeSet:", changeSet=0x00006080001af260) at ASDataController.mm:561
frame #3: 0x000000010b44372f AsyncDisplayKit`::-[ASTableView endUpdatesAnimated:completion:](self=0x00007fb453834400, _cmd="endUpdatesAnimated:completion:", animated=YES, completion=(null))(BOOL)) at ASTableView.mm:718
frame #4: 0x000000010b44310a AsyncDisplayKit`::-[ASTableView endUpdatesWithCompletion:](self=0x00007fb453834400, _cmd="endUpdatesWithCompletion:", completion=(null))(BOOL)) at ASTableView.mm:699
frame #5: 0x000000010b44307f AsyncDisplayKit`::-[ASTableView endUpdates](self=0x00007fb453834400, _cmd="endUpdates") at ASTableView.mm:693
frame #6: 0x000000010b444637 AsyncDisplayKit`::-[ASTableView reloadSections:withRowAnimation:](self=0x00007fb453834400, _cmd="reloadSections:withRowAnimation:", sections=1 index, animation=UITableViewRowAnimationAutomatic) at ASTableView.mm:801
frame #7: 0x000000010b43a67f AsyncDisplayKit`::-[ASTableNode reloadSections:withRowAnimation:](self=0x00007fb450421230, _cmd="reloadSections:withRowAnimation:", sections=1 index, animation=UITableViewRowAnimationAutomatic) at ASTableNode.mm:730
* frame #8: 0x000000010a7fa4d8 Relisten`closure #1 in ArtistsViewController.resourceChanged(resource=0x00007fb450558350, self=0x00007fb45400b200) at ArtistsViewController.swift:205
frame #9: 0x000000010a7fab01 Relisten`partial apply for closure #1 in ArtistsViewController.resourceChanged(_:event:) at ArtistsViewController.swift:0
Credit needs to be given for the icons from the Noun Project
I'm guessing some of the projects used via Cocoapods also require (or at least deserve) credit.
Relisten should have a credits view somewhere- since there's not in-app settings right now maybe the best place is in the per-app stuff in Settings.app?
If another application is playing audio when PhishOD launches that audio stops when BASS_Init()
is called. The call stack leading to that is:
* frame #0: BASSGaplessAudioPlayer`-[ObjectiveBASS setupBASS](self=0x00007f8753241e00, _cmd="setupBASS") at ObjectiveBASS.m:277
frame #1: BASSGaplessAudioPlayer`-[ObjectiveBASS init](self=0x00007f8753241e00, _cmd="init") at ObjectiveBASS.m:266
frame #2: AGAudioPlayer`-[AGAudioPlayer setupBASS](self=0x000060000047a540, _cmd="setupBASS") at AGAudioPlayer.m:368
frame #3: AGAudioPlayer`-[AGAudioPlayer setup](self=0x000060000047a540, _cmd="setup") at AGAudioPlayer.m:61
frame #4: AGAudioPlayer`-[AGAudioPlayer initWithQueue:](self=0x000060000047a540, _cmd="initWithQueue:", queue=0x000060c00023b9a0) at AGAudioPlayer.m:55
frame #5: Relisten`@nonobjc AGAudioPlayer.init(queue:) at PlaybackController.swift:0
frame #6: Relisten`AGAudioPlayer.__allocating_init(queue:) at PlaybackController.swift:0
frame #7: Relisten`PlaybackController.init() at PlaybackController.swift:38
frame #8: Relisten`PlaybackController.__allocating_init() at PlaybackController.swift:0
frame #9: Relisten`globalinit_33_CEB0CB2109270705D0E6647B36802A82_func1 at PlaybackController.swift:34
frame #10: libdispatch.dylib`_dispatch_client_callout + 8
frame #11: libdispatch.dylib`dispatch_once_f + 285
frame #12: Relisten`PlaybackController.sharedInstance.unsafeMutableAddressor at PlaybackController.swift:34
frame #13: Relisten`AppDelegate.setupPlayback(self=0x0000608000226880) at RLAppDelegate.swift:79
frame #14: Relisten`AppDelegate.application(application=0x00007f8752700ac0, launchOptions=nil, self=0x0000608000226880) at RLAppDelegate.swift:69
frame #15: Relisten`@objc AppDelegate.application(_:didFinishLaunchingWithOptions:) at RLAppDelegate.swift:0
If you accidentally tap on the download all button it's not easy to cancel that action. The button should change to a progress button and tapping it should cancel all in-progress downloads.
The horizontal show views for On This Date and Recently Played in the Phish artist view doesn't appear, even though they have shows loaded when I view them in the debugger.
It's confusing to see shows for all artists listed under "favorites" when I don't have any favorite artists selected yet. That section should be hidden until some favorite artists have been picked.
I noticed that my recent tracks were disappearing shortly after they were played. They'd show up in the recent tracks view, but then they'd disappear a few seconds later.
I finally chased this down to Firestore refusing the save of the library and silently reverting back to the server's data:
2018-07-09 16:14:10.387710-0700 Relisten[10129:315605] 5.0.0 - [Firebase/Firestore][I-FST000001] FSTWriteStream 0x60800011a280 close: Error Domain=FIRFirestoreErrorDomain Code=3 "A document cannot be written because it exceeds the maximum size allowed."
I just have 12 recently played tracks, but it looks like the JSON for each track is pretty big, since it contains all of the artist/show/source information, including every track in the show. Those 12 tracks end up being over 1MB of JSON data.
Keep a cache of streamed tracks to make more songs available for offline use
Forward to me and @farktronix?
If I play/pause from the app's mini player controller then the system now playing UI doesn't appear to ever update.
Artwork was disabled in CarPlay due to slow performance- loading the shows for a year, then going back to the year view, then back in to shows would sometimes take upwards of two minutes for the view to present.
Let's get to the bottom of the performance and re-enable artwork in CarPlay.
Relisten needs a settings view. This can also contain the credits needed for #84. It should contain some of the same stuff as the previous version- links to GitHub and version information.
For now the only other setting is to enable/disable shake to report a bug. We can also just turn off shake to report and have a button in settings to file a bug that jumps to the GitHub issues page.
Probably about 1/2 the size
Looks like this view needs to be hooked up
Note: this is quite rough and thought of late a night. This will require revisions:
All objects eligble for persistence have a uuid
property.
Objects with a uuid
property vary in size. Some are consistent and small, like an Artist
. Some objects can be large or small—such as ShowWithSources
. The number of sources within the show or the corresponding descriptions can vary wildly from show to show—even within the same artist.
However, in theory, this is not a problem. Even a very large show with many sources isn’t completely unwieldy since the largest variable (reviews) is behind a separate API request now.
What we are really talking about is a difference between a couple hundred bytes and a couple kilobytes. Does this make a substantial difference on a modern iPhone? Let’s do the math, Fermi estimation style:
A true fan of a band might have 100 shows favorited. A truly rediculous show like Grateful Dead’s 5/8/1978 is a 281 KiB raw JSON response, but for a Fermi estimation let’s call it 200 KiB of resident memory. This means a wild fan of a band would need 200 KiB * 100 = 20,000 KiB = 20 MiB
of memory to hold all of their favorite shows within instant reach.
To be frank, 20 MiB isn’t a ton of memory and it may be premature optimization to break things down further/smaller than that. Even if we held twice as many objects in memory—which is more shows than many bands have played—we are still safely in a range where even a power-user on paltry iPhone 5S with 1GiB of RAM would be happy. We can’t give up 20 MiB here and 20 MiB there and come out with an acceptable memory usage (despite being ill-defined) but cutting out Firebase completely gives us a chunk of memory back (exact amount unknown).
Summary: Just hold all the shows in memory, but disk backed for persistence—it is easier and doesn’t enough memory to be even a minor issue.
Siesta provides a really cool pipeline stage called cleanup
that happens after the JSON response has been parsed into models. We can hook into this stage of the pipeline only on routes that return “persistent” models (ones with a uuid
) using configure(whenURLMatches:requestMethods:description:configurer:)
.
We can use this to automatically store any ShowWithSources
into different disk backed caches with 50 MiB automatically held in memory using Cache
.
We can write to the cache anytime we receive a response and read from it whenever we want. Then, to maintain a “favorites” list we just have to store a list of uuid
s and hydrate that from the cache whenever we need to access it. In general, the favorited shows should stay in memory and be quick to access. In the worst case, we need to go to the SSD and fetch it.
Because we do not sync, something can only be in favorites if it has been accessed on that device and we do not need to worry about cache misses which fetching the list of favorites. Of course, it is prudent to still handle (aka skip that show) this scenario despite it being “impossible” like so many programming bugs.
We should not hold an array of all the shows in memory that gets invalidated any time a show is favorited/un-favorited. This would double memory usage for no real benefit. We can assemble the whole list of shows from only the uuid
s with very little overhead despite it feeling “wrong”. It might not be the most optimal, but it is great for our purposes because:
Yay open source.
ShowWithSources
entires with a key of show_uuid
__tracks$$
that holds a [String: (show_uuid: String, source_uuid: String)]
mapping. This is held in memory manually and is persisted on every mutation.__sources$$
that holds a [String: String]
mapping from source UUIDs to show UUIDs__favoriteShows$$
that holds a [FavoriteShow]
__favoriteSources$$
that holds a [FavoriteSource]
__favoriteTracks$$
that holds a [FavoriteTrack]
__playbackQueue$$
that holds a PlaybackResortation
for restoring playback on launchstruct PlaybackRestoration {
public let queue: [UUID] = [] // track UUIDs
public let active: UUID? = nil // active track UUID
public let playbackPosition: TimeInterval? = nil // position in active track
}
struct FavoriteShow {
public let show_uuid: UUID
public let created_at: Date
}
struct FavoriteSource {
public let source_uuid: UUID
public let created_at: Date
}
struct FavoriteTrack {
public let track_uuid: UUID
public let created_at: Date
}
struct SourceFullInShowWithSources {
public let show: ShowWithSources
public let source: SourceFull
}
struct SourceTrackInSourceFullInShowWithSources {
public let show: ShowWithSources
public let source: SourceFull
public let track: SourceTrack
}
show(byUUID: UUID) -> ShowWithSources
source(byUUID: UUID) -> SourceFullInShowWithSources
__sources$$
to lookup the show uuid
show(forSourceUUID uuid: UUID) -> ShowWithSources
source(byUUID: uuid).show
show(byTrackUUID uuid: UUID) -> ShowWithSources
track(byUUID: uuid).show
source(byTrackUUID uuid: UUID) -> SourceFull
track(byUUID: uuid).source
track(byUUID uuid: UUID) -> SourceTrackInSourceFullInShowWithSources
__tracks$$
to find the show uuid
save(show: ShowWithSources)
__tracks$$
for every track in every set__sources$$
for every sourceaddFavorite(show: Show, in show: ShowWithSources)
__favoriteShows$$
save(show: show)
addFavorite(source: Source, in show: ShowWithSources)
__favoriteSources$$
save(show: show)
addFavorite(track: SourceTrack, in show: ShowWithSources)
__favoriteTracks$$
save(show: show)
removeFavorite(_ uuid: UUID) -> Bool
savePlaybackRestorationInformation(_ info: PlaybackRestoration)
public let favoriteShows: Observable<[ShowWithSources]>
public let favoriteSources: Observable<[SourceFullInShowWithSources]>
favoriteSourcesIncludingIndirect() -> [SourceFullInShowWithSources]
public let favoriteTracks: Observable<[SourceTrackInSourceFullInShowWithSources]>
favoriteTracksIncludingIndirect() -> [SourceTrackInSourceFullInShowWithSources]
Easy here because the special keys are just cached version of things fetched on the server. Updated before any change—like git pull --rebase origin master
—then change is applied. Also updated on any app open. No offline support for modifying favorites.
Commit 71d7220 made the menu items always expanded, but that looks a little odd now. These buttons need a redesign.
How can I opt into the beta? Is there a testflight?
If I tap the << button to seek backwards one track from control center or the now playing view on the lock screen the next track in the queue starts playing instead of the previous track.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.