Skip to content

Commit

Permalink
Merge pull request #94 from lamarios/feature/sponsor_block_granularity
Browse files Browse the repository at this point in the history
Feature/sponsor block granularity
  • Loading branch information
lamarios authored Apr 5, 2023
2 parents 96c41a3 + dbcdb71 commit 9601c89
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 20 deletions.
17 changes: 11 additions & 6 deletions lib/controllers/playerController.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import '../models/db/settings.dart';
import '../models/pair.dart';
import '../../models/db/progress.dart' as dbProgress;
import '../models/sponsorSegment.dart';
import '../models/sponsorSegmentTypes.dart';
import '../models/video.dart';
import 'miniPayerController.dart';

class PlayerController extends GetxController {
static PlayerController? to() => safeGet();

final log = Logger('VideoPlayer');
bool useSponsorBlock = db.getSettings(USE_SPONSORBLOCK)?.value == 'true';
List<Pair<int>> sponsorSegments = List.of([]);
Pair<int> nextSegment = Pair(0, 0);
BetterPlayerController? videoController;
Expand Down Expand Up @@ -53,7 +53,6 @@ class PlayerController extends GetxController {

@override
onReady() async {
await setSponsorBlock();
playVideo();
}

Expand Down Expand Up @@ -88,7 +87,7 @@ class PlayerController extends GetxController {
saveProgress(currentPosition);
log.info("video event");

if (useSponsorBlock && sponsorSegments.isNotEmpty) {
if (sponsorSegments.isNotEmpty) {
double positionInMs = currentPosition * 1000;
Pair<int> nextSegment = sponsorSegments.firstWhere((e) => e.first <= positionInMs && positionInMs <= e.last, orElse: () => Pair<int>(-1, -1));
if (nextSegment.first != -1) {
Expand Down Expand Up @@ -160,6 +159,8 @@ class PlayerController extends GetxController {
}

playVideo() {
// we get segments if there are any, no need to wait.
setSponsorBlock();
double progress = db.getVideoProgress(video.videoId);
Duration? startAt;
if (progress > 0 && progress < 0.90) {
Expand Down Expand Up @@ -231,8 +232,10 @@ class PlayerController extends GetxController {
}

setSponsorBlock() async {
if (useSponsorBlock) {
List<SponsorSegment> sponsorSegments = await service.getSponsorSegments(video.videoId);
List<SponsorSegmentType> types = SponsorSegmentType.values.where((e) => db.getSettings(e.settingsName())?.value == 'true').toList();

if (types.isNotEmpty) {
List<SponsorSegment> sponsorSegments = await service.getSponsorSegments(video.videoId, types);
List<Pair<int>> segments = List.from(sponsorSegments.map((e) {
Duration start = Duration(seconds: e.segment[0].floor());
Duration end = Duration(seconds: e.segment[1].floor());
Expand All @@ -241,7 +244,9 @@ class PlayerController extends GetxController {
}));

this.sponsorSegments = segments;
update();
log.info('we found ${segments.length} segments to skip');
} else {
sponsorSegments = [];
}
}
}
14 changes: 14 additions & 0 deletions lib/controllers/sponsorBlockSettingsController.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:get/get.dart';
import 'package:invidious/database.dart';
import 'package:invidious/globals.dart';
import 'package:invidious/models/db/settings.dart';
import 'package:invidious/models/sponsorSegmentTypes.dart';

class SponsorBlockSettingsController extends GetxController {
bool value(SponsorSegmentType t) => db.getSettings(t.settingsName())?.value == 'true';

setValue(SponsorSegmentType t, bool value) {
db.saveSetting(SettingsValue(t.settingsName(), value.toString()));
update();
}
}
1 change: 1 addition & 0 deletions lib/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'objectbox.g.dart'; // created by `flutter pub run build_runner build`

const SELECTED_SERVER = 'selected-server';
const USE_SPONSORBLOCK = 'use-sponsor-block';
const SPONSOR_BLOCK_PREFIX='sponsor-block-';
const BROWSING_COUNTRY = 'browsing-country';
const DYNAMIC_THEME = 'dynamic-theme';
const USE_DASH = 'use-dash';
Expand Down
68 changes: 68 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,74 @@
"addRecommendedToQueue": "Auto-play recommended next",
"@addRecommendedToQueue": {
"description": "Switch when playing a video to automatically add the recommended videos to the video queue"
},
"sponsorBlockSettingsQuickDescription": "Select which type of segments to skip",
"@sponsorBlockSettingsQuickDescription": {
"description": "Small description of what the sponsor block settings do"
},
"sponsorBlockCategorySponsor": "Sponsor",
"@sponsorBlockCategorySponsor": {
"description": "Sponsor block 'Sponsor' Category"
},
"sponsorBlockCategorySponsorDescription": "Paid promotion, paid referrals and direct advertisements. Not for self-promotion or free shoutouts to causes/creators/websites/products they like.",
"@sponsorBlockCategorySponsorDescription": {
"description": "Sponsor block 'Sponsor' Category description"
},
"sponsorBlockCategoryUnpaidSelfPromo": "Unpaid/Self Promotion",
"@sponsorBlockCategoryUnpaidSelfPromo": {
"description": "Sponsor block 'Unpaid/Self promotion' Category"
},
"sponsorBlockCategoryUnpaidSelfPromoDescription": "Similar to \"sponsor\" except for unpaid or self promotion. This includes sections about merchandise, donations, or information about who they collaborated ",
"@sponsorBlockCategoryUnpaidSelfPromoDescription": {
"description": "Sponsor block 'Unpaid/Self promotion' Category description"
},
"sponsorBlockCategoryInteraction": "Interaction Reminder (Subscribe)",
"@sponsorBlockCategoryInteraction": {
"description": "Sponsor block 'Interaction' Category"
},
"sponsorBlockCategoryInteractionDescription": "When there is a short reminder to like, subscribe or follow them in the middle of content. If it is long or about something specific, it should be under self promotion instead.",
"@sponsorBlockCategoryInteractionDescription": {
"description": "Sponsor block 'Interaction' Category description"
},
"sponsorBlockCategoryIntro": "Intermission/Intro Animation",
"@sponsorBlockCategoryIntro": {
"description": "Sponsorblock 'Intro' Category"
},
"sponsorBlockCategoryIntroDescription": "An interval without actual content. Could be a pause, static frame, repeating animation. This should not be used for transitions containing information.",
"@sponsorBlockCategoryIntroDescription": {
"description": "Sponsorblock 'Intro' Category description"
},
"sponsorBlockCategoryOutro": "Endcards/Credits",
"@sponsorBlockCategoryOutro": {
"description": "Outro block 'Outro' Category"
},
"sponsorBlockCategoryOutroDescription": "Credits or when the YouTube endcards appear. Not for conclusions with information.",
"@sponsorBlockCategoryOutroDescription": {
"description": "Outro block 'Outro' Category description"
},
"sponsorBlockCategoryPreview": "Preview/Recap",
"@sponsorBlockCategoryPreview": {
"description": "Sponsorblock 'Preview' Category"
},
"sponsorBlockCategoryPreviewDescription": "Collection of clips that show what is coming up in in this video or other videos in a series where all information is repeated later in the video",
"@sponsorBlockCategoryPreviewDescription": {
"description": "Sponsorblock 'Preview' Category description"
},
"sponsorBlockCategoryFiller": "Filler Tangent/Jokes",
"@sponsorBlockCategoryFiller": {
"description": "Sponsorblock 'Filler' Category"
},
"sponsorBlockCategoryFillerDescription": "Tangential scenes added only for filler or humor that are not required to understand the main content of the video. This should not include segments providing context or background details. This is a very aggressive category meant for when you aren''t in the mood for \"fun\".",
"@sponsorBlockCategoryFillerDescription": {
"description": "Sponsorblock 'Filler' Category description"
},
"sponsorBlockCategoryMusicOffTopic": "Music: Non-Music Section",
"@sponsorBlockCategoryMusicOffTopic": {
"description": "Sponsorblock 'MusicOffTopic' Category"
},
"sponsorBlockCategoryMusicOffTopicDescription": "An interval without actual content. Could be a pause, static frame, repeating animation. This should not be used for transitions containing information.",
"@sponsorBlockCategoryMusicOffTopicDescription": {
"description": "Only for use in music videos. This only should be used for sections of music videos that aren't already covered by another category."
},
"useProxy": "Proxy videos",
"@useProxy": {
Expand Down
4 changes: 3 additions & 1 deletion lib/models/sponsorSegment.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:invidious/models/sponsorSegmentTypes.dart';
import 'package:json_annotation/json_annotation.dart';
/*
Expand All @@ -21,8 +22,9 @@ part 'sponsorSegment.g.dart';
class SponsorSegment{
String actionType;
List<double> segment;
SponsorSegmentType category;

SponsorSegment(this.actionType, this.segment);
SponsorSegment(this.actionType, this.segment, this.category);

factory SponsorSegment.fromJson(Map<String, dynamic> json) => _$SponsorSegmentFromJson(json);

Expand Down
13 changes: 13 additions & 0 deletions lib/models/sponsorSegment.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions lib/models/sponsorSegmentTypes.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:invidious/database.dart';

enum SponsorSegmentType {
sponsor,
selfpromo,
interaction,
intro,
outro,
preview,
music_offtopic,
filler;

String settingsName() => '$SPONSOR_BLOCK_PREFIX$name';

static String getLabel(SponsorSegmentType type, AppLocalizations locals) {
switch (type) {
case SponsorSegmentType.sponsor:
return locals.sponsorBlockCategorySponsor;
case SponsorSegmentType.selfpromo:
return locals.sponsorBlockCategoryUnpaidSelfPromo;
case SponsorSegmentType.interaction:
return locals.sponsorBlockCategoryInteraction;
case SponsorSegmentType.intro:
return locals.sponsorBlockCategoryIntro;
case SponsorSegmentType.outro:
return locals.sponsorBlockCategoryOutro;
case SponsorSegmentType.preview:
return locals.sponsorBlockCategoryPreview;
case SponsorSegmentType.music_offtopic:
return locals.sponsorBlockCategoryMusicOffTopic;
case SponsorSegmentType.filler:
return locals.sponsorBlockCategoryFiller;
}
}

static String getDescription(SponsorSegmentType type, AppLocalizations locals) {
switch (type) {
case SponsorSegmentType.sponsor:
return locals.sponsorBlockCategorySponsorDescription;
case SponsorSegmentType.selfpromo:
return locals.sponsorBlockCategoryUnpaidSelfPromoDescription;
case SponsorSegmentType.interaction:
return locals.sponsorBlockCategoryInteractionDescription;
case SponsorSegmentType.intro:
return locals.sponsorBlockCategoryIntroDescription;
case SponsorSegmentType.outro:
return locals.sponsorBlockCategoryOutroDescription;
case SponsorSegmentType.preview:
return locals.sponsorBlockCategoryPreviewDescription;
case SponsorSegmentType.music_offtopic:
return locals.sponsorBlockCategoryMusicOffTopicDescription;
case SponsorSegmentType.filler:
return locals.sponsorBlockCategoryFillerDescription;
}
}
}
4 changes: 3 additions & 1 deletion lib/myRouteObserver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'globals.dart';
const RouteSettings ROUTE_SETTINGS = RouteSettings(name: 'settings');
const RouteSettings ROUTE_SETTINGS_MANAGE_SERVERS = RouteSettings(name: 'settings-manage-servers');
const RouteSettings ROUTE_SETTINGS_MANAGE_ONE_SERVER = RouteSettings(name: 'settings-manage-one-server');
const RouteSettings ROUTE_SETTINGS_SPONSOR_BLOCK = RouteSettings(name: 'settings-sponsor-block');
const RouteSettings ROUTE_VIDEO = RouteSettings(name: 'video');
const RouteSettings ROUTE_CHANNEL = RouteSettings(name: 'channel');
const RouteSettings ROUTE_PLAYLIST_LIST = RouteSettings(name: 'playlist-list');
Expand All @@ -25,6 +26,7 @@ class MyRouteObserver extends RouteObserver<PageRoute<dynamic>> {
case ROUTE_PLAYLIST:
case ROUTE_SETTINGS_MANAGE_SERVERS:
case ROUTE_SETTINGS_MANAGE_ONE_SERVER:
case ROUTE_SETTINGS_SPONSOR_BLOCK:
case ROUTE_VIDEO:
case ROUTE_PLAYLIST_LIST:
case ROUTE_CHANNEL:
Expand All @@ -42,7 +44,7 @@ class MyRouteObserver extends RouteObserver<PageRoute<dynamic>> {
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPush(route, previousRoute);
if (route is PageRoute) {
if(previousRoute is PageRoute){
if (previousRoute is PageRoute) {
stopPlayingOnPop(route, previousRoute);
}
}
Expand Down
29 changes: 23 additions & 6 deletions lib/service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'models/channelVideos.dart';
import 'models/db/server.dart';
import 'models/invidiousPublicServer.dart';
import 'models/searchSuggestion.dart';
import 'models/sponsorSegmentTypes.dart';
import 'models/subscription.dart';
import 'models/videoComments.dart';

Expand Down Expand Up @@ -82,7 +83,9 @@ class Service {

Uri buildUrl(String baseUrl, {Map<String, String>? pathParams, Map<String, String?>? query}) {
try {
String url = '${db.getCurrentlySelectedServer().url}$baseUrl';
String url = '${db
.getCurrentlySelectedServer()
.url}$baseUrl';

pathParams?.forEach((key, value) {
url = url.replaceAll(key, value);
Expand Down Expand Up @@ -147,7 +150,9 @@ class Service {
String url = '$serverUrl/authorize_token?scopes=:feed,:subscriptions*,:playlists*&callback_url=clipious-auth://';
final result = await FlutterWebAuth.authenticate(url: url, callbackUrlScheme: 'clipious-auth');

final token = Uri.parse(result).queryParameters['token'];
final token = Uri
.parse(result)
.queryParameters['token'];

Server? server = db.getServer(serverUrl);

Expand Down Expand Up @@ -176,7 +181,9 @@ class Service {
}

Future<List<VideoInList>> getTrending({String? type}) async {
String countryCode = db.getSettings(BROWSING_COUNTRY)?.value ?? 'US';
String countryCode = db
.getSettings(BROWSING_COUNTRY)
?.value ?? 'US';
// parse.queryParameters['region'] = countryCode;
Map<String, String>? query = {'region': countryCode};

Expand Down Expand Up @@ -234,9 +241,15 @@ class Service {
return UserFeed.fromJson(handleResponse(response));
}

Future<List<SponsorSegment>> getSponsorSegments(String videoId) async {
Future<List<SponsorSegment>> getSponsorSegments(String videoId, List<SponsorSegmentType> categories) async {
try {
String url = GET_SPONSOR_SEGMENTS.replaceAll(":id", videoId);

if (categories.isNotEmpty) {
url += '&categories=[${categories.map((e) => '"${e.name}"').join(",")}]';
}


log.info('Calling $url');
final response = await http.get(Uri.parse(url));
Iterable i = handleResponse(response);
Expand Down Expand Up @@ -408,14 +421,18 @@ class Service {
}

Future<Duration?> pingServer(String url) async {
int start = DateTime.now().millisecondsSinceEpoch;
int start = DateTime
.now()
.millisecondsSinceEpoch;
String fullUri = '$url${STATS}';
log.info('calling ${fullUri}');
final response = await http.get(Uri.parse(fullUri), headers: {'Content-Type': 'application/json; charset=utf-16'});

try {
handleResponse(response);
var diff = DateTime.now().millisecondsSinceEpoch - start;
var diff = DateTime
.now()
.millisecondsSinceEpoch - start;
return Duration(milliseconds: diff);
} catch (err) {
log.info(err);
Expand Down
Loading

0 comments on commit 9601c89

Please sign in to comment.