Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 77 additions & 2 deletions assets/js/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
var player_data = JSON.parse(document.getElementById('player_data').textContent);
var video_data = JSON.parse(document.getElementById('video_data').textContent);

const STORAGE_MARK_WATCHED_AFTER_DURATION = "mark_watched_after_duration";

var options = {
liveui: true,
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
Expand Down Expand Up @@ -126,13 +128,37 @@ function addCurrentTimeToURL(url, base) {
*/
var timeupdate_last_ts = 5;

/**
* Global variable to save the total video time watched (in seconds).
*/
let time_watched = 0;

/**
* The duration of a short video (in seconds).
* This value is used to determine whether the video should be watched fully before
* being marked as watched.
*
* @default 30
*/
const SHORT_VIDEO_DURATION = 30;

/**
* The duration (in seconds) after which a video should be marked as watched.
*
* @default 30
*/
const MARK_WATCHED_AFTER_DURATION = SHORT_VIDEO_DURATION;

/**
* Callback that updates the timestamp on all external links
* Callback that updates the timestamp on all external links and marks the video as watched after:
* - fully watching short videos (<=30 seconds)
* - time watched reaches 30 seconds for long videos (>30 seconds)
*/
player.on('timeupdate', function () {
// Only update once every second
let current_ts = Math.floor(player.currentTime());
if (current_ts > timeupdate_last_ts) timeupdate_last_ts = current_ts;
const last_player_time = timeupdate_last_ts;
if (current_ts !== timeupdate_last_ts) timeupdate_last_ts = current_ts;
else return;

// YouTube links
Expand Down Expand Up @@ -166,6 +192,55 @@ player.on('timeupdate', function () {
let base_url_iv_other = elem_iv_other.getAttribute('data-base-url');
elem_iv_other.href = addCurrentTimeToURL(base_url_iv_other, domain);
}

// Check if the feature is enabled
const $markWatchedAfterDuration = document.getElementById(`${STORAGE_MARK_WATCHED_AFTER_DURATION}_pref`);
const markWatchedAfterDuration = $markWatchedAfterDuration?.innerText === "true";

if (!markWatchedAfterDuration) return;

// Only increase time watched when the time difference is one second and the video has not been marked as watched
const isOneSecondDifference = current_ts - last_player_time === 1;
const exceedsMarkWatchedAfterDuration = time_watched > MARK_WATCHED_AFTER_DURATION;

if (!isOneSecondDifference || exceedsMarkWatchedAfterDuration) return;

time_watched += 1;

// Check if time watched exceeds 30 seconds or the video is fully watched
const absolute_video_duration = Math.floor(player.duration());
const watched_at_timestamp = absolute_video_duration > SHORT_VIDEO_DURATION
? MARK_WATCHED_AFTER_DURATION
: absolute_video_duration;

if (time_watched !== watched_at_timestamp) return;

const video_id = document.querySelector('[name="id"]').value;
const $csrfToken = document.querySelector('[name="csrf_token"]');

// User is not logged in
if ($csrfToken === null) return;

// Mark the video as watched
const csrf_token = $csrfToken.value;

const params = new URLSearchParams({
action: "mark_watched",
redirect: false,
id: video_id
});

const url = `/watch_ajax?${params}`;

helpers.xhr('POST', url, { payload: `csrf_token=${csrf_token}` }, {
on200: () => {
console.info(`Marked video ${video_id} as watched`);
},
onNon200: ({ response }) => {
console.error(`Something went wrong while marking video ${video_id} as watched:`, response);
}
});

});


Expand Down
1 change: 1 addition & 0 deletions locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"preferences_listen_label": "Listen by default: ",
"preferences_local_label": "Proxy videos: ",
"preferences_watch_history_label": "Enable watch history: ",
"preferences_mark_watched_after_duration_label": "Mark video as watched after watching for some time: ",
"preferences_speed_label": "Default speed: ",
"preferences_quality_label": "Preferred video quality: ",
"preferences_quality_option_dash": "DASH (adaptive quality)",
Expand Down
1 change: 1 addition & 0 deletions src/invidious/config.cr
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct ConfigPreferences
property vr_mode : Bool = true
property show_nick : Bool = true
property save_player_pos : Bool = false
property mark_watched_after_duration : Bool = false
@[YAML::Field(ignore: true)]
property default_playlist : String? = nil

Expand Down
5 changes: 5 additions & 0 deletions src/invidious/routes/preferences.cr
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ module Invidious::Routes::PreferencesRoute
save_player_pos ||= "off"
save_player_pos = save_player_pos == "on"

mark_watched_after_duration = env.params.body["mark_watched_after_duration"]?.try &.as(String)
mark_watched_after_duration ||= "off"
mark_watched_after_duration = mark_watched_after_duration == "on"

show_nick = env.params.body["show_nick"]?.try &.as(String)
show_nick ||= "off"
show_nick = show_nick == "on"
Expand Down Expand Up @@ -182,6 +186,7 @@ module Invidious::Routes::PreferencesRoute
vr_mode: vr_mode,
show_nick: show_nick,
save_player_pos: save_player_pos,
mark_watched_after_duration: mark_watched_after_duration,
default_playlist: default_playlist,
}.to_json)

Expand Down
4 changes: 0 additions & 4 deletions src/invidious/routes/watch.cr
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ module Invidious::Routes::Watch
end
env.params.query.delete_all("iv_load_policy")

if watched && preferences.watch_history
Invidious::Database::Users.mark_watched(user.as(User), id)
end

if CONFIG.enable_user_notifications && notifications && notifications.includes? id
Invidious::Database::Users.remove_notification(user.as(User), id)
env.get("user").as(User).notifications.delete(id)
Expand Down
1 change: 1 addition & 0 deletions src/invidious/user/preferences.cr
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ struct Preferences
property extend_desc : Bool = CONFIG.default_user_preferences.extend_desc
property volume : Int32 = CONFIG.default_user_preferences.volume
property save_player_pos : Bool = CONFIG.default_user_preferences.save_player_pos
property mark_watched_after_duration : Bool = CONFIG.default_user_preferences.mark_watched_after_duration
property default_playlist : String? = nil

module BoolToString
Expand Down
54 changes: 30 additions & 24 deletions src/invidious/videos/video_preferences.cr
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ struct VideoPreferences
property volume : Int32
property vr_mode : Bool
property save_player_pos : Bool
property mark_watched_after_duration : Bool
end

def process_video_params(query, preferences)
Expand All @@ -48,6 +49,7 @@ def process_video_params(query, preferences)
volume = query["volume"]?.try &.to_i?
vr_mode = query["vr_mode"]?.try { |q| (q == "true" || q == "1").to_unsafe }
save_player_pos = query["save_player_pos"]?.try { |q| (q == "true" || q == "1").to_unsafe }
mark_watched_after_duration = query["mark_watched_after_duration"]?.try { |q| (q == "true" || q == "1").to_unsafe }

if preferences
# region ||= preferences.region
Expand All @@ -70,6 +72,7 @@ def process_video_params(query, preferences)
volume ||= preferences.volume
vr_mode ||= preferences.vr_mode.to_unsafe
save_player_pos ||= preferences.save_player_pos.to_unsafe
mark_watched_after_duration ||= preferences.mark_watched_after_duration.to_unsafe
end

annotations ||= CONFIG.default_user_preferences.annotations.to_unsafe
Expand All @@ -91,6 +94,7 @@ def process_video_params(query, preferences)
volume ||= CONFIG.default_user_preferences.volume
vr_mode ||= CONFIG.default_user_preferences.vr_mode.to_unsafe
save_player_pos ||= CONFIG.default_user_preferences.save_player_pos.to_unsafe
mark_watched_after_duration ||= CONFIG.default_user_preferences.mark_watched_after_duration.to_unsafe

annotations = annotations == 1
preload = preload == 1
Expand All @@ -104,6 +108,7 @@ def process_video_params(query, preferences)
extend_desc = extend_desc == 1
vr_mode = vr_mode == 1
save_player_pos = save_player_pos == 1
mark_watched_after_duration = mark_watched_after_duration == 1

if CONFIG.disabled?("dash") && quality == "dash"
quality = "high"
Expand Down Expand Up @@ -132,30 +137,31 @@ def process_video_params(query, preferences)
controls = controls >= 1

params = VideoPreferences.new({
annotations: annotations,
preload: preload,
autoplay: autoplay,
comments: comments,
continue: continue,
continue_autoplay: continue_autoplay,
controls: controls,
listen: listen,
local: local,
player_style: player_style,
preferred_captions: preferred_captions,
quality: quality,
quality_dash: quality_dash,
raw: raw,
region: region,
related_videos: related_videos,
speed: speed,
video_end: video_end,
video_loop: video_loop,
extend_desc: extend_desc,
video_start: video_start,
volume: volume,
vr_mode: vr_mode,
save_player_pos: save_player_pos,
annotations: annotations,
preload: preload,
autoplay: autoplay,
comments: comments,
continue: continue,
continue_autoplay: continue_autoplay,
controls: controls,
listen: listen,
local: local,
player_style: player_style,
preferred_captions: preferred_captions,
quality: quality,
quality_dash: quality_dash,
raw: raw,
region: region,
related_videos: related_videos,
speed: speed,
video_end: video_end,
video_loop: video_loop,
extend_desc: extend_desc,
video_start: video_start,
volume: volume,
vr_mode: vr_mode,
save_player_pos: save_player_pos,
mark_watched_after_duration: mark_watched_after_duration,
})

return params
Expand Down
4 changes: 4 additions & 0 deletions src/invidious/views/template.ecr
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<%
locale = env.get("preferences").as(Preferences).locale
dark_mode = env.get("preferences").as(Preferences).dark_mode
mark_watched_after_duration = env.get("preferences").as(Preferences).mark_watched_after_duration
%>
<!DOCTYPE html>
<html lang="<%= locale %>">
Expand All @@ -27,6 +28,9 @@

<body class="<%= dark_mode.blank? ? "no" : dark_mode %>-theme">
<span style="display:none" id="dark_mode_pref"><%= dark_mode %></span>
<% if env.get? "user" %>
<span style="display:none" id="mark_watched_after_duration_pref"><%= mark_watched_after_duration %></span>
<% end %>
<div class="pure-g">
<div class="pure-u-1 pure-u-xl-20-24" id="contents">
<div class="pure-g navbar h-box">
Expand Down
5 changes: 5 additions & 0 deletions src/invidious/views/user/preferences.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@
<input name="watch_history" id="watch_history" type="checkbox" <% if preferences.watch_history %>checked<% end %>>
</div>

<div class="pure-control-group">
<label for="mark_watched_after_duration"><%= translate(locale, "preferences_mark_watched_after_duration_label") %></label>
<input name="mark_watched_after_duration" id="mark_watched_after_duration" type="checkbox" <% if preferences.mark_watched_after_duration %>checked<% end %>>
</div>

<div class="pure-control-group">
<label for="annotations_subscribed"><%= translate(locale, "preferences_annotations_subscribed_label") %></label>
<input name="annotations_subscribed" id="annotations_subscribed" type="checkbox" <% if preferences.annotations_subscribed %>checked<% end %>>
Expand Down