diff --git a/src/models/meta_details.rs b/src/models/meta_details.rs
index 47a995999..76b68e341 100644
--- a/src/models/meta_details.rs
+++ b/src/models/meta_details.rs
@@ -123,20 +123,9 @@ impl<E: Env + 'static> UpdateWithCtx<E> for MetaDetails {
                 is_watched,
             ))) => match (&self.library_item, &self.watched) {
                 (Some(library_item), Some(watched)) => {
-                    let mut watched = watched.to_owned();
-                    watched.set_video(&video.id, *is_watched);
                     let mut library_item = library_item.to_owned();
-                    library_item.state.watched = Some(watched.into());
-                    if *is_watched {
-                        library_item.state.last_watched =
-                            match (&library_item.state.last_watched, &video.released) {
-                                (Some(last_watched), Some(released)) if last_watched < released => {
-                                    Some(released.to_owned())
-                                }
-                                (None, released) => released.to_owned(),
-                                (last_watched, _) => last_watched.to_owned(),
-                            };
-                    }
+                    library_item.mark_video_as_watched::<E>(watched, video, *is_watched);
+
                     Effects::msg(Msg::Internal(Internal::UpdateLibraryItem(library_item)))
                         .unchanged()
                 }
diff --git a/src/models/player.rs b/src/models/player.rs
index e5576e923..4c87a7851 100644
--- a/src/models/player.rs
+++ b/src/models/player.rs
@@ -658,6 +658,31 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
                 }))
                 .unchanged()
             }
+            Msg::Action(Action::Player(ActionPlayer::MarkVideoAsWatched(video, is_watched))) => {
+                match (&self.library_item, &self.watched) {
+                    (Some(library_item), Some(watched)) => {
+                        let mut library_item = library_item.to_owned();
+                        library_item.mark_video_as_watched::<E>(watched, video, *is_watched);
+
+                        Effects::msg(Msg::Internal(Internal::UpdateLibraryItem(library_item)))
+                            .unchanged()
+                    }
+                    _ => Effects::none().unchanged(),
+                }
+            }
+            Msg::Internal(Internal::LibraryChanged(_)) => {
+                let library_item_effects = library_item_update::<E>(
+                    &mut self.library_item,
+                    &self.selected,
+                    &self.meta_item,
+                    &ctx.library,
+                );
+
+                let watched_effects =
+                    watched_update(&mut self.watched, &self.meta_item, &self.library_item);
+
+                library_item_effects.join(watched_effects)
+            }
             Msg::Internal(Internal::StreamsChanged(_)) => {
                 stream_state_update(&mut self.stream_state, &self.selected, &ctx.streams)
             }
@@ -1052,10 +1077,7 @@ fn library_item_update<E: Env + 'static>(
             meta_request: Some(meta_request),
             ..
         }) => {
-            let library_item = library_item
-                .as_ref()
-                .filter(|library_item| library_item.id == meta_request.path.id)
-                .or_else(|| library.items.get(&meta_request.path.id));
+            let library_item = library.items.get(&meta_request.path.id);
             let meta_item = meta_item.as_ref().and_then(|meta_item| match meta_item {
                 ResourceLoadable {
                     content: Some(Loadable::Ready(meta_item)),
@@ -1077,28 +1099,7 @@ fn library_item_update<E: Env + 'static>(
         }
         _ => None,
     };
-    if *library_item != next_library_item {
-        let update_library_item_effects = match &library_item {
-            Some(library_item) => Effects::msg(Msg::Internal(Internal::UpdateLibraryItem(
-                library_item.to_owned(),
-            )))
-            .unchanged(),
-            _ => Effects::none().unchanged(),
-        };
-        let update_next_library_item_effects = match &next_library_item {
-            Some(next_library_item) => Effects::msg(Msg::Internal(Internal::UpdateLibraryItem(
-                next_library_item.to_owned(),
-            )))
-            .unchanged(),
-            _ => Effects::none().unchanged(),
-        };
-        *library_item = next_library_item;
-        Effects::none()
-            .join(update_library_item_effects)
-            .join(update_next_library_item_effects)
-    } else {
-        Effects::none().unchanged()
-    }
+    eq_update(library_item, next_library_item)
 }
 
 fn watched_update(
diff --git a/src/runtime/msg/action.rs b/src/runtime/msg/action.rs
index 70cecee2d..15b6c5692 100644
--- a/src/runtime/msg/action.rs
+++ b/src/runtime/msg/action.rs
@@ -191,6 +191,12 @@ pub enum ActionPlayer {
     /// - We've watched a movie to the last second
     /// - We've watched a movie series to the last second
     Ended,
+    /// Marks the given [`Video`] of the [`LibraryItem`] as watched.
+    ///
+    /// Applicable only when you have a multi-video (e.g. movie series) item.
+    ///
+    /// [`LibraryItem`]: crate::types::library::LibraryItem
+    MarkVideoAsWatched(Video, bool),
 }
 
 #[derive(Clone, Deserialize, Debug)]
diff --git a/src/types/library/library_item.rs b/src/types/library/library_item.rs
index d771ebed2..3d1143bc6 100644
--- a/src/types/library/library_item.rs
+++ b/src/types/library/library_item.rs
@@ -106,6 +106,28 @@ impl LibraryItem {
             self.state.times_watched = 0;
         }
     }
+
+    pub fn mark_video_as_watched<E: Env>(
+        &mut self,
+        watched: &WatchedBitField,
+        video: &Video,
+        is_watched: bool,
+    ) {
+        let mut watched = watched.to_owned();
+        watched.set_video(&video.id, is_watched);
+
+        self.state.watched = Some(watched.into());
+
+        if is_watched {
+            self.state.last_watched = match (&self.state.last_watched, &video.released) {
+                (Some(last_watched), Some(released)) if last_watched < released => {
+                    Some(released.to_owned())
+                }
+                (None, released) => released.to_owned(),
+                (last_watched, _) => last_watched.to_owned(),
+            };
+        }
+    }
 }
 
 impl<E: Env + 'static> From<(&MetaItemPreview, PhantomData<E>)> for LibraryItem {
diff --git a/stremio-core-web/src/model/serialize_player.rs b/stremio-core-web/src/model/serialize_player.rs
index f712a4717..67e930963 100644
--- a/stremio-core-web/src/model/serialize_player.rs
+++ b/stremio-core-web/src/model/serialize_player.rs
@@ -146,16 +146,10 @@ pub fn serialize_player<E: stremio_core::runtime::Env + 'static>(
                             video,
                             upcoming: meta_item.preview.behavior_hints.has_scheduled_videos
                                 && video.released > Some(E::now()),
-                            watched: ctx
-                                .library
-                                .items
-                                .get(&meta_item.preview.id)
-                                .map(|library_item| {
-                                    library_item
-                                        .state
-                                        .watched_bitfield(&meta_item.videos)
-                                        .get_video(&video.id)
-                                })
+                            watched: player
+                                .watched
+                                .as_ref()
+                                .map(|watched| watched.get_video(&video.id))
                                 .unwrap_or_default(),
                             // only the currently playing video can have the progress
                             // as we keep that information in the LibraryItem