diff --git a/Media/play_state_sync.sh b/Media/play_state_sync.sh index 4aaf32b..672eb9d 100755 --- a/Media/play_state_sync.sh +++ b/Media/play_state_sync.sh @@ -426,10 +426,14 @@ for lname in "${!USER_MAP[@]}"; do _pc="${E_PCOUNT[$_si]:-0}" _tk="${E_TICKS[$_si]:-0}" _pf="${E_PLAYED[$_si]:-false}" - if [[ "$_e" -gt "$_auth_epoch" ]] || \ - [[ "$_e" -eq "$_auth_epoch" && "$_pc" -gt "$_auth_pcount" ]] || \ - [[ "$_e" -eq "$_auth_epoch" && "$_pc" -eq "$_auth_pcount" && "$_tk" -gt "$_auth_ticks" ]] || \ - [[ "$_e" -eq "$_auth_epoch" && "$_pc" -eq "$_auth_pcount" && "$_tk" -eq "$_auth_ticks" && "$_pf" == "true" && "$_auth_pf" != "true" ]]; then + # Played=true is the primary key — a played server always beats a non-played server + # regardless of epoch. A resumable item with a newer LastPlayedDate must not become + # authority over a played item, as pushing resume ticks to a played server resets + # the played status on some Emby/Jellyfin versions. + if ( [[ "$_pf" == "true" ]] && [[ "$_auth_pf" != "true" ]] ) || \ + ( [[ "$_pf" == "$_auth_pf" ]] && [[ "$_e" -gt "$_auth_epoch" ]] ) || \ + ( [[ "$_pf" == "$_auth_pf" ]] && [[ "$_e" -eq "$_auth_epoch" ]] && [[ "$_pc" -gt "$_auth_pcount" ]] ) || \ + ( [[ "$_pf" == "$_auth_pf" ]] && [[ "$_e" -eq "$_auth_epoch" ]] && [[ "$_pc" -eq "$_auth_pcount" ]] && [[ "$_tk" -gt "$_auth_ticks" ]] ); then _auth_si="$_si" _auth_epoch="$_e" _auth_pcount="$_pc" @@ -527,7 +531,13 @@ for lname in "${!USER_MAP[@]}"; do (( TOTAL_ERRORS++ )) fi else - # Sync resume position only + # Sync resume position only — never push ticks to a server that already + # has this item marked played. Writing ticks via UserData can reset Played=false. + if [[ "${E_PLAYED[$_si]:-false}" == "true" ]]; then + log " SKIP $_pkey → ${SRV_NAME[$_si]} already played, not overwriting with resume ticks" + (( TOTAL_SKIPPED++ )) + continue + fi _ticks_int=$(( ${_auth_ticks:-0} )) if [[ "$_ticks_int" -gt 0 ]]; then _payload="{\"PlaybackPositionTicks\":${_ticks_int}}"