mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-05-30 23:40:40 +02:00
[Bug]: Days in a Row doesn't count sessions started before midnight #3237
Closed
opened 2026-04-25 00:14:26 +02:00 by adam
·
10 comments
No Branch/Tag Specified
master
book_tags_genres_dedupe
episode_download_fallback
Issue-4540-SortBy-StartedDate-and-FinishedDate
episode_meta_tagging
fix_authorize_race_condition
redirect_transcode_requests
progress_updated_sort
fix_ereader_socket_event
fix_change_empty_root_password
fix_podcast_session_track_index
fix_set_token
session_modal_user
localize_durations
fix_oidc_create_user
jwt_auth_refactor
fix_scanner_deleting_single_file_books
fix_mediaprogress_updatedat_2
experimental_next_client
podcast_episode_duration
episode-timestamps-clickable
book_author_secondary_sort_title
podcast_useragents
pathexists_user_access
fix_pathexists_join
book_author_secondary_sort
clean_duplicate_mediaprogress
sanitize_html_description
trix_prevent_attachments
check_path_api_fix
fix_mediaprogress_updatedat
increase_express_json_limit
fix_dockerfile_nunicode
search_episodes
audiobook_tools_update
episode_secondary_sorts
hls_stream_url_update
new_session_track_endpoint
audiobook_tools_enhancements
watcher_rescans_update
player_track_tooltip
fix_exclude_prefixes_crash
socket_item_events
fix_podcast_episode_scanner_promise
new_stats_controller
count_cache_for_userpermissions
parsing-opf-v3
validate_migration_files
fix-quick-match-all-crash
fix-chapter-end-sleep-timer
stringify_sequelize_query
remove-col-ambiguity
fix_next_prev_edit_description
details_trim_whitespace
fix_content_url_basepath
fix_logger_fatal
progress_bar_visibility
batch-edit-populate-map-details
feed_generator_updates
bookmark-modal-updates
migrate-library-item-in-scanner
migrate-new-library-items
migrate-podcasts-new-library-item-2
migrate-podcasts-new-library-item
fix-remove-episode-from-playlist
playback-session-use-new-library-item
refactor-library-item
fix-heatmap-caption
feed-episodes-upsert
share-media-player-media-session-api
remove-old-playlist
remove_old_collection_object
plugin-implementation-demo
feed_migration
refactor-feeds-from-item
fix_remove_authors_no_books
v2.17.3-fk-constraints-migration
migrations-first-upgrade
sqlite_2
feature/nuxt-target-server
waveform
sqlite
playlists
video
v2.35.1
v2.35.0
v2.34.0
v2.33.2
v2.33.1
v2.33.0
v2.32.1
v2.32.0
v2.31.0
v2.30.0
v2.29.0
v2.28.0
v2.27.0
v2.26.3
v2.26.2
v2.26.1
v2.26.0
v2.25.1
v2.25.0
v2.24.0
v2.23.0
v2.22.0
v2.21.0
v2.20.0
v2.19.5
v2.19.4
v2.19.3
v2.19.2
v2.19.1
v2.19.0
v2.18.1
v2.18.0
v2.17.7
v2.17.6
v2.17.5
v2.17.4
v2.17.3
v2.17.2
v2.17.1
v2.17.0
v2.16.2
v2.16.1
v2.16.0
v2.15.1
v2.15.0
v2.14.0
v2.13.4
v2.13.3
v2.13.2
v2.13.1
v2.13.0
v2.12.3
v2.12.2
v2.12.1
v2.12.0
v2.11.0
v2.10.1
v2.10.0
v2.9.0
v2.8.1
v2.8.0
v2.7.2
v2.7.1
v2.7.0
v2.6.0
v2.5.0
v2.4.4
v2.4.3
v2.4.2
v2.4.1
v2.4.0
v2.3.5
v2.3.4
v2.3.3
v2.3.2
v2.3.1
v2.3.0
v2.2.23
v2.2.22
v2.2.21
v2.2.20
v2.2.19
v2.2.18
v2.2.17
v2.2.16
v2.2.15
v2.2.14
v2.2.13
v2.2.12
v2.2.11
v2.2.10
v2.2.9
v2.2.8
v2.2.7
v2.2.6
v2.2.5
v2.2.4
v2.2.3
v2.2.2
v2.2.1
v2.2.0
v2.1.5
v2.1.4
v2.1.3
v2.1.2
v2.1.1
v2.1.0
v2.0.24
v2.0.23
v2.0.22
v2.0.21
v2.0.20
v2.0.19
v2.0.18
v2.0.17
v2.0.16
v2.0.15
v2.0.14
v2.0.13
v2.0.12
v2.0.11
v2.0.10
v2.0.9
v2.0.8
v2.0.7
v2.0.6
v2.0.5
v2.0.4
v2.0.3
v2.0.2
v2.0.1
v1.7.2
v1.7.1
v1.7.0
v1.6.0
v1.5.5
v1.5.0
v1.4.11
v1.4.9
v1.4.7
v1.4.6
v1.4.4
v1.4.2
v1.4.0
v1.4.1
v1.3.4
v1.3.3
v1.3.1
v1.2.8
v1.2.6
v1.2.5
v1.2.4
v1.2.1
v1.1.15
v1.1.14
v1.1.13
v1.1.12
v1.1.11
v1.1.10
v1.1.9
v1.1.8
v1.0.0
0.9.61-beta.0
0.9.61-beta
Labels
Clear labels
authentication
backlog
bug
chapter editor
config-issue
ebooks
encoding/embedding
enhancement
help wanted
listening sessions & progress
planned
possible plugin
progress sync
pull-request
sorting/filtering/searching
unable to reproduce
upload
users & permissions
waiting
Mirrored from GitHub Pull Request
No Label
bug
Milestone
No items
No Milestone
Projects
Clear projects
No project
Assignees
adam (Adam Melkus)
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: starred/audiobookshelf#3237
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @pwinnski on GitHub (Feb 14, 2026).
What happened?
Yesterday, I started listening to an audiobook at 23:41 local time. I stopped at 00:19 this morning. Logs show the server was notified as it was happening.
There are a number of updates, and then the final update when I stopped:
Since I didn't stop listening until after midnight, and that was my only listening session for that day, my server stats show 0 minutes for yesterday, rather than 19, and my streak reset to 0. So now my client shows a 176-day listening streak, while the server shows a 1-day streak, and they will apparently by 175 days out of sync unless I do some manual database editing.
What did you expect to happen?
I would expect sessions that cross date boundaries to have listen time split between the days, and to count for both days when tracking the listening streak.
Steps to reproduce the issue
Audiobookshelf version
v2.32.1
How are you running audiobookshelf?
Docker
What OS is your Audiobookshelf server hosted from?
macOS
If the issue is being seen in the UI, what browsers are you seeing the problem on?
None
Logs
Additional Notes
Poking around, I think maybe this could be fixed in
ApiRouter#getUserListeningStatsHelpers(), so I'm going to dig into that.@Vito0912 commented on GitHub (Feb 14, 2026):
Duplicate of https://github.com/advplyr/audiobookshelf/issues/1445
TL;DR ABS has no way of tracking this atm and it is impossible to retroactively add this missing data without some heuristic
@pwinnski commented on GitHub (Feb 14, 2026):
Oops, I searched two different terms, but missed that one, sorry.
I don't think it's a question of retroactively modifying stored data, nor of tracking anything new. Since all of the complaints revolve around the stats displayed by
DailyListeningChart.vue, it seems like modifyinggetUserListeningStatsHelpers()to return different results whens.createdAtands.updatedAtare on different days might solve the problem, no?The session itself has started and ended on different dates:
I think that's all fine and good. The only issue is this display:
Which seems to be based entirely on the return value of
getUserListeningStatsHelpers(). I'm poking at that code now, so perhaps I'll find out I'm wrong.@Vito0912 commented on GitHub (Feb 14, 2026):
Then you have the other problem of users saying they started yesterday, but it was only counted today. There is
nono that is flawless solution for this.You can of course change the logic, but it will only be as good as what three timestamps can give you. You could do some calculation and split the percentage listened between yesterday and today, and so on. But that is why I said it is a heuristic. You will not be able to fix this, only move the problem to another side or make it less extreme, for example by splitting it by percentage.
Also not to say, that updatedAt can be way in the future (e.g. syncing it later, updating it later)
@pwinnski commented on GitHub (Feb 15, 2026):
One of us is not understanding the other, or perhaps both. I'm definitely not suggesting shifting everything to another day! Very few sessions cross midnight, so I'm only suggesting doing something slightly different in those cases. And yes, perhaps only "making it less extreme" by handling the simplest cases which are currently mishandled. A session that results in 38 minutes of progress on a chart 19 minutes after midnight is... odd.
IF we have a start time and an end time (which I realize is a big IF), then sessions themselves are fine, but
listeningStatscould easily be split so that instead instead of addingsessionTimeListeningto thedays[s.date]bucket, it addssessionTimeListeningAfterMidnightto that bucket, andsessionTimeListeningBeforeMidnightto thedays[s.date - 1]bucket, so to speak. Not those names, of course, and not that syntax, but conceptually. Then activity reflects reality, at least forlisteningStats. I already have that coded, and have been testing it with web-based and client playback sessions, and poking around and learning. As you say, any solution is only as good as the timestamps, so pausing for extended periods and such is interesting.If clients aren't providing accurate session start and end times, then there's no way around that, of course. But let's not let unattainable perfection be the enemy of good!
In the meantime, I'm fascinated by the interaction between
getUserListeningStatsHelpers()andDailyListeningChart.vue, and learning some interesting things.@Vito0912 commented on GitHub (Feb 15, 2026):
No, I understand that. But the PR ignores that updatedAt has absolutely nothing to do with the timeListened. The relation can only be connected in a Pperfect ABS usage.
A session can be updated months or years later. Pausing, some clients and changing sessions, all will alter the updatedAt.
But anyway maybe the PR will get merged still idk (just a contributer)
@pwinnski commented on GitHub (Feb 15, 2026):
Sure, I understand. I did test with my mobile client and deliberately disabled wifi to cause a gap, but only by some minutes, not days. But of course, that's true now, and has the same effect on stats now. My PR doesn't make that situation worse, and defaults to using
s.daterather thans.updatedAteverywhere possible to ensure behavior doesn't change at all if a session is anything close to normal. I would have ignoreds.updatedAtcompletely, buts.dateis a VarChar of just the date rather than a DateTime, so I reasoned that in the absence of any real time data, I'd take what I could get, since it could be no worse, and would usually be better.One note: pausing, some clients, changing sessions, and so on, all of the things that can change
updatedAt, none of them matter at all unless the date ofs.startedAt(akacreatedAt) ands.datediffer. So the impact of this is as minimized as possible, and relies ons.dateas much as possible. So just now I tested with an "accurate"createdAtbut anupdatedAtfour days later, and several different times. In all cases, the results are better than the status quo, and only the time ofupdateAtmatters, not the date.P.S. Such modesty; you're a very active contributor. Also, I hope I haven't come across as negative; I've very much appreciated your feedback, and iterated through quite a few approaches to address as much as possible of your comments, most especially in preserving the exact current functionality when
createdAtanddateare the same.@Vito0912 commented on GitHub (Feb 15, 2026):
Sorry, to answer again. I think we are talking past each other, and I know I explain things very badly. That is why I do not answer issues much lately. Also, please do not take my messages as mean or anything. Some people do, but I do not want that.
So this is my last comment on this in the end it is not my decision. But I think it would break a lot of stats for users (or more do nothing, but see below) - depending on the app.
To keep it short, there are 3 or 4 important variables for us (I never named the
date, but see below):date- The actual date when the session startedstartedAt- As you said, created at. I need to correct myself. Every time I wrote createdAt in my old comments, I actually meantdate(but notstartedAt). This does not have to be equal todate, but mostly is.updatedAt- Already discussed abovetimeListening- The actual listening timeA user can pause for an hour and still be in the same session. If a user started at 1PM, paused an hour from 2 PM to 3PM and closes the session at 4PM, the updatedAt will be 3 hours apart, while only listening two. This is not uncommon at all.
The
dateis the first time a person started the session. So if I start listening on 02/15/2026 at 23:59, it will show this date, no matter how long I listen.Ref: https://github.com/advplyr/audiobookshelf/blob/master/server/objects/PlaybackSession.js#L237
startedAtandupdatedAtare independent, because they do not have to be set. But even if we assumestartedAtis set,startedAtwill be about the same asdate. If notstartedAteither will be an arbirtary timestamp by an app or the time the session is created the first time (this is important if it's a local session, thendateandstartedAtcould be different depending on the app. But one thing they defintily will not be that one is the started and one is the ended date)So unless I miss something obvious and to my knwoeldge, the statement
if (startDate === s.date) {in your PR will always be true. But even if it is not, and we enter the else statement, you again depend fully onupdatedAt, which brings back all the initial issues I mentioned above.@pwinnski commented on GitHub (Feb 15, 2026):
NOW I understand! What you are missing is that
dateis not the actual date the session started, but the date the session ended. This is easily demonstrably true with actual testing, and is the entire reason I created the issue and started looking into this in the first place!Absolutely guaranteed, the session I started at 11:41pm local time on Friday, 2026-02-13, and ended at 12:19am local time on Saturday, 2026-02-14, has a
dateof2026-02-14and adayOfWeekofSaturday. See the logs in the initial report above, and this record from the db:If what you said were accurate, I would have had no issue! The report would have shown activity on Friday and my streak would have continued. I mean, I told you, and also put in the PR, that I tested this many, many, many times. Clearly I saw that the else clause is taken whenever a session crosses midnight. Repeatedly!
You've linked to code that is called in a single place: PlaybackSessionManager.syncSession(), a method in which the next line is:
Scroll back up to my logs and note that isn't what my logs show. Rather than
syncSession(), my logs show that my server was usingsyncLocalSession(), which doesn't setdateonly on the first update, but on every update.Ref: https://github.com/advplyr/audiobookshelf/blob/master/server/managers/PlaybackSessionManager.js#L216
So
syncLocalSession()means that every session will havedateanddayOfWeekset to the end of the session, rather than the beginning. Which it does. Hence the broken chart and broken streak and the issue.I'm going to close the PR and just manually modify the database rather than keep going around about this. This whole flood of comments from you as someone with a high reputation reduces the already-low chances of the PR getting positive attention to below zero. Perhaps someday someone will look into why
syncSession()andsyncLocalSession()behave so differently, but not me, not today. I've put in too many hours staying up past midnight to test using real actual activity, so possibly it's sleep-deprivation talking, but this just doesn't seem worth it, despite all my efforts to ensure things got better rather than worse. They can stay worse.@Vito0912 commented on GitHub (Feb 15, 2026):
I don't really understand why you closed the PR. Again, I have nothing to say about it getting merged or not.
The link you shared is for syncing local sessions if you are offline and are not connected to the internet, which makes a lot of sense, why it did not really affect that many people. Thus it's also NOT a duplicate of what I posted initally.
Some corrections
From the code you linked this seems also incorrect. The date indeed gets set to the first time the session gets created the first time (after then the if !session will return true and no date updates occur [most third party clients use it that way])). But indeed it takes the last time, which really is a bit inconsistent with it. So this condition is only true for complete offline sessions that gets synced the first time. If that is after midnight then you have this issue.
I think it would make sense to change the title and reopen the issue to show the inconsistency here
And thanks for showing the oversight!
TL;DR: This only happens if you use local sessions that have an updatedAt after midnight and a
created/startedAtbefore midnight, because the date of the local sessions get calculated on the first creation from theupdatedAt, while non local sessions get the date set basically bystartedAtWith that information in mind it might even be worth tbh. Still not a perfect solution, but it will not be destructive for syncs and it explains why it did happen to you and not to me while testing. Again sorry for the oversight in the whole conv. Normally it's the other way around, that people want syncing in the other direction
And for why I did say
But one thing they defintily will not be that one is the started and one is the ended date, is because I looked at the update code for local sessions not for the creation code, my bad (because as in the TL;DR, the date is calculated the first time the session is synced, but is ignored after that completely). I think we all can agree that not updating the date is bad in general, especially with such inconsistent behaviour.@Vito0912 commented on GitHub (Feb 15, 2026):
I updated my comment in that regard. With this in mind it actually makes sense to some degree. I understood it the way that it counted the day before, like the linked issue. I could have seen it by the image tbh, but I didn't and as you didn't mention that it was a wrong linked issue I just made wrong assumptions. I stand by my word that it is still no perfect solution, but it will fix your problem and it should do in a non destructive way for others. (because this way can't really be caused logically by any other means)
It's just important to note that this will only fix the local session sync, which why it didn't work when I tested it.
(Just a message ping, so you see it)