Skip to content

Commit

Permalink
Merge branch 'development' into feat/streming-server-urls-bucket
Browse files Browse the repository at this point in the history
  • Loading branch information
kKaskak committed Nov 18, 2024
2 parents 7cb5664 + 2a199b6 commit 4d6b954
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 51 deletions.
176 changes: 128 additions & 48 deletions src/models/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ pub struct Player {
pub seek_history: Vec<SeekLog>,
#[serde(skip_serializing)]
pub skip_gaps: Option<(SkipGapsRequest, Loadable<SkipGapsResponse, CtxError>)>,
/// Enable or disable Seek log collection.
///
/// Default: `false` (Do not collect)
#[serde(default, skip_serializing)]
pub collect_seek_logs: bool,
}

impl<E: Env + 'static> UpdateWithCtx<E> for Player {
Expand Down Expand Up @@ -380,6 +385,73 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
}))
.unchanged()
}
Msg::Action(Action::Player(ActionPlayer::Seek {
time,
duration,
device,
})) => match (&self.selected, &mut self.library_item) {
(
// make sure we have a Selected
Some(_selected),
Some(library_item),
) => {
// We might want to consider whether we want to update the LibraryItem for next video
// like we do for TimeChanged

// update the last_watched
library_item.state.last_watched = Some(E::now());

if self.collect_seek_logs {
// collect seek history
if library_item.r#type == "series" && time < &PLAYER_IGNORE_SEEK_AFTER {
self.seek_history.push(SeekLog {
from: library_item.state.time_offset,
to: *time,
});
}
}
// };
time.clone_into(&mut library_item.state.time_offset);
duration.clone_into(&mut library_item.state.duration);
// No need to check and flag the library item as watched,
// seeking does not update the time_watched!

// Nor there's a need to update removed and temp, this can only happen
// after we mark a LibraryItem as watched! Leave this to TimeChanged

// Update the analytics, we still want to keep the correct time and duration updated
if let Some(analytics_context) = &mut self.analytics_context {
library_item
.state
.video_id
.clone_into(&mut analytics_context.video_id);
analytics_context.time = Some(library_item.state.time_offset);
analytics_context.duration = Some(library_item.state.duration);
analytics_context.device_type = Some(device.to_owned());
analytics_context.device_name = Some(device.to_owned());
analytics_context.player_duration = Some(duration.to_owned());
};

// on seeking we want to make sure we send the correct Trakt events
let trakt_event_effects = match (self.loaded, self.paused) {
(true, Some(true)) => Effects::msg(Msg::Event(Event::TraktPaused {
context: self.analytics_context.as_ref().cloned().unwrap_or_default(),
}))
.unchanged(),
(true, Some(false)) => Effects::msg(Msg::Event(Event::TraktPlaying {
context: self.analytics_context.as_ref().cloned().unwrap_or_default(),
}))
.unchanged(),
_ => Effects::none(),
};

let push_to_library_effects =
push_to_library::<E>(&mut self.push_library_item_time, library_item);

trakt_event_effects.join(push_to_library_effects)
}
_ => Effects::none().unchanged(),
},
Msg::Action(Action::Player(ActionPlayer::TimeChanged {
time,
duration,
Expand All @@ -396,8 +468,6 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
}),
Some(library_item),
) => {
let seeking = library_item.state.time_offset.abs_diff(*time) > 1000;

// if we've selected a new video (like the next episode)
library_item.state.last_watched = Some(E::now());
if library_item.state.video_id != Some(video_id.to_owned()) {
Expand All @@ -409,29 +479,26 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
library_item.state.time_watched = 0;
library_item.state.flagged_watched = 0;
} else {
// else we have added to the currently selected video/stream
// seek logging
if seeking
&& library_item.r#type == "series"
&& time < &PLAYER_IGNORE_SEEK_AFTER
{
self.seek_history.push(SeekLog {
from: library_item.state.time_offset,
to: *time,
});
}

let time_watched =
1000.min(time.saturating_sub(library_item.state.time_offset));
let time_watched = time.saturating_sub(library_item.state.time_offset);
library_item.state.time_watched =
library_item.state.time_watched.saturating_add(time_watched);
library_item.state.overall_time_watched = library_item
.state
.overall_time_watched
.saturating_add(time_watched);
};
time.clone_into(&mut library_item.state.time_offset);
duration.clone_into(&mut library_item.state.duration);

// if we seek forward, time will be < time_offset
// this is the only thing we can guard against!
//
// for both backward and forward seeking we expect the apps to
// send the right actions and update the times accordingly
// when the state changes (from seeking to playing and vice versa)
if time > &library_item.state.time_offset {
time.clone_into(&mut library_item.state.time_offset);
duration.clone_into(&mut library_item.state.duration);
}

if library_item.state.flagged_watched == 0
&& library_item.state.time_watched as f64
> library_item.state.duration as f64 * WATCHED_THRESHOLD_COEF
Expand All @@ -444,13 +511,16 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
watched_bit_field.set_video(video_id, true);
library_item.state.watched = Some(watched_bit_field.into());
}
};
}

if library_item.temp && library_item.state.times_watched == 0 {
library_item.removed = true;
};
}

if library_item.removed {
library_item.temp = true;
};
}

if let Some(analytics_context) = &mut self.analytics_context {
library_item
.state
Expand All @@ -462,34 +532,8 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
analytics_context.device_name = Some(device.to_owned());
analytics_context.player_duration = Some(duration.to_owned());
};
let trakt_event_effects = if seeking && self.loaded && self.paused.is_some() {
if self.paused.expect("paused is None") {
Effects::msg(Msg::Event(Event::TraktPaused {
context: self
.analytics_context
.as_ref()
.cloned()
.unwrap_or_default(),
}))
.unchanged()
} else {
Effects::msg(Msg::Event(Event::TraktPlaying {
context: self
.analytics_context
.as_ref()
.cloned()
.unwrap_or_default(),
}))
.unchanged()
}
} else {
Effects::none()
};

let push_to_library_effects =
push_to_library::<E>(&mut self.push_library_item_time, library_item);

trakt_event_effects.join(push_to_library_effects)
push_to_library::<E>(&mut self.push_library_item_time, library_item)
}
_ => Effects::none().unchanged(),
},
Expand Down Expand Up @@ -529,6 +573,42 @@ impl<E: Env + 'static> UpdateWithCtx<E> for Player {
};
trakt_event_effects.join(update_library_item_effects)
}
Msg::Action(Action::Player(ActionPlayer::PausedChanged { paused }))
if self.selected.is_some() =>
{
self.paused = Some(*paused);
let trakt_event_effects = if !self.loaded {
self.loaded = true;
Effects::msg(Msg::Event(Event::PlayerPlaying {
load_time: self
.load_time
.map(|load_time| {
E::now().timestamp_millis() - load_time.timestamp_millis()
})
.unwrap_or(-1),
context: self.analytics_context.as_ref().cloned().unwrap_or_default(),
}))
.unchanged()
} else if *paused {
Effects::msg(Msg::Event(Event::TraktPaused {
context: self.analytics_context.as_ref().cloned().unwrap_or_default(),
}))
.unchanged()
} else {
Effects::msg(Msg::Event(Event::TraktPlaying {
context: self.analytics_context.as_ref().cloned().unwrap_or_default(),
}))
.unchanged()
};
let update_library_item_effects = match &self.library_item {
Some(library_item) => Effects::msg(Msg::Internal(Internal::UpdateLibraryItem(
library_item.to_owned(),
)))
.unchanged(),
_ => Effects::none().unchanged(),
};
trakt_event_effects.join(update_library_item_effects)
}
Msg::Action(Action::Player(ActionPlayer::NextVideo)) => {
let seek_history_effects = seek_update::<E>(
self.selected.as_ref(),
Expand Down
17 changes: 17 additions & 0 deletions src/runtime/msg/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,23 @@ pub enum ActionPlayer {
StreamStateChanged {
state: StreamItemState,
},
/// Seek performed by the user when using the seekbar or
/// the shortcuts for seeking.
///
/// When transitioning from Seek to TimeChanged and vice-versa
/// we need to make sure to update the other accordingly
/// if we have any type of throttling of these events,
/// otherwise we will get wrong `LibraryItem.state.time_offset`!
Seek {
time: u64,
duration: u64,
device: String,
},
/// A normal playback by the video player
///
/// The time from one TimeChanged action to another can only grow (move forward)
/// and should never go backwards, except when a [`ActionPlayer::Seek`] happen
/// and moves the time backwards.
TimeChanged {
time: u64,
duration: u64,
Expand Down
4 changes: 2 additions & 2 deletions src/types/streams/streams_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ pub struct StreamItemState {
pub subtitle_delay: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
/// Subtitles size, platform dependent units
pub subtitle_size: Option<u64>,
pub subtitle_size: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
/// Vertical offset of the subtitles, platform dependent units
pub subtitle_offset: Option<u64>,
pub subtitle_offset: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub audio_track: Option<AudioTrack>,
/// In milliseconds
Expand Down
5 changes: 4 additions & 1 deletion stremio-core-web/src/model/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ impl WebModel {
installed_addons,
addon_details: Default::default(),
streaming_server,
player: Default::default(),
player: Player {
collect_seek_logs: true,
..Default::default()
},
};
(
model,
Expand Down

0 comments on commit 4d6b954

Please sign in to comment.