mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-11-01 21:23:38 -04:00
1048 lines
54 KiB
Plaintext
1048 lines
54 KiB
Plaintext
#import "Headers/iSponsorBlock.h"
|
|
#import <AudioToolbox/AudioToolbox.h>
|
|
#import <rootless.h>
|
|
#import "Headers/ColorFunctions.h"
|
|
#import "Headers/SponsorBlockSettingsController.h"
|
|
#import "Headers/SponsorBlockRequest.h"
|
|
#import "Headers/SponsorBlockViewController.h"
|
|
|
|
#define LOC(x) [tweakBundle localizedStringForKey:x value:nil table:nil]
|
|
|
|
extern "C" NSBundle *iSponsorBlockBundle() {
|
|
static NSBundle *bundle = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
NSString *tweakBundlePath = [[NSBundle mainBundle] pathForResource:@"iSponsorBlock" ofType:@"bundle"];
|
|
if (tweakBundlePath)
|
|
bundle = [NSBundle bundleWithPath:tweakBundlePath];
|
|
else
|
|
bundle = [NSBundle bundleWithPath:ROOT_PATH_NS("/Library/Application Support/iSponsorBlock.bundle")];
|
|
});
|
|
return bundle;
|
|
}
|
|
|
|
NSBundle *tweakBundle = iSponsorBlockBundle();
|
|
|
|
// Sound effect for skip segments
|
|
static void playSponsorAudio() {
|
|
NSString *audioFilePath = [tweakBundle pathForResource:@"SponsorAudio" ofType:@"m4a"];
|
|
NSURL *audioFileURL = [NSURL fileURLWithPath:audioFilePath];
|
|
SystemSoundID soundID;
|
|
AudioServicesCreateSystemSoundID((__bridge CFURLRef)audioFileURL, &soundID);
|
|
AudioServicesPlaySystemSound(soundID);
|
|
}
|
|
|
|
// Check and translate segment title for HUD
|
|
NSDictionary *categoryLocalization = @{
|
|
@"sponsor": LOC(@"sponsor"),
|
|
@"intro": LOC(@"intro"),
|
|
@"outro": LOC(@"outro"),
|
|
@"interaction": LOC(@"interaction"),
|
|
@"selfpromo": LOC(@"selfpromo"),
|
|
@"music_offtopic": LOC(@"music_offtopic")
|
|
};
|
|
|
|
%group Main
|
|
NSString *modifiedTimeString;
|
|
|
|
%hook YTPlayerViewController
|
|
%property (strong, nonatomic) NSMutableArray *skipSegments;
|
|
%property (nonatomic, assign) NSInteger currentSponsorSegment;
|
|
%property (strong, nonatomic) MBProgressHUD *hud;
|
|
%property (nonatomic, assign) NSInteger unskippedSegment;
|
|
%property (strong, nonatomic) NSMutableArray *userSkipSegments;
|
|
%property (strong, nonatomic) NSString *channelID;
|
|
%property (nonatomic, assign) BOOL hudDisplayed;
|
|
|
|
// used to keep support for older versions, as seekToTime is new
|
|
%new
|
|
- (void)isb_scrubToTime:(CGFloat)time {
|
|
// YT v17.30.1 switched scrubToTime to seekToTime
|
|
[self respondsToSelector:@selector(scrubToTime:)] ? [self scrubToTime:time] : [self seekToTime:time];
|
|
}
|
|
|
|
- (void)singleVideo:(id)arg1 currentVideoTimeDidChange:(YTSingleVideoTime *)arg2 {
|
|
%orig;
|
|
YTPlayerView *playerView = (YTPlayerView *)self.view;
|
|
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
|
|
if (!self.channelID) self.channelID = @"";
|
|
if (self.skipSegments.count > 0 && [overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)] && ![kWhitelistedChannels containsObject:self.channelID]) {
|
|
if (kShowModifiedTime) {
|
|
UILabel *durationLabel = overlayView.playerBar.durationLabel;
|
|
if (![durationLabel.text containsString:modifiedTimeString]) durationLabel.text = [NSString stringWithFormat:@"%@ (%@)", durationLabel.text, modifiedTimeString];
|
|
[durationLabel sizeToFit];
|
|
}
|
|
|
|
SponsorSegment *sponsorSegment = [[SponsorSegment alloc] initWithStartTime:-1 endTime:-1 category:nil UUID:nil];
|
|
if (self.currentSponsorSegment <= self.skipSegments.count-1) {
|
|
sponsorSegment = self.skipSegments[self.currentSponsorSegment];
|
|
} else if (self.unskippedSegment != self.currentSponsorSegment-1) {
|
|
sponsorSegment = self.skipSegments[self.currentSponsorSegment-1];
|
|
}
|
|
|
|
if ((lroundf(arg2.time) == ceil(sponsorSegment.startTime) && arg2.time >= sponsorSegment.startTime) || (lroundf(arg2.time) >= ceil(sponsorSegment.startTime) && arg2.time < sponsorSegment.endTime)) {
|
|
|
|
if ([[kCategorySettings objectForKey:sponsorSegment.category] intValue] == 3) {
|
|
if (self.hud.superview != self.view && self.hudDisplayed == NO) {
|
|
self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
|
|
self.hudDisplayed = YES; // Set yes to make sure that HUD is not persistent (Issue #62)
|
|
self.hud.mode = MBProgressHUDModeCustomView;
|
|
NSString *localizedSegment = categoryLocalization[sponsorSegment.category] ?: sponsorSegment.category;
|
|
NSString *localizedManualSkip = LOC(@"ManuallySkipReminder");
|
|
NSString *formattedManualSkip = [NSString stringWithFormat:localizedManualSkip, localizedSegment, lroundf(sponsorSegment.startTime)/60, lroundf(sponsorSegment.startTime)%60, lroundf(sponsorSegment.endTime)/60, lroundf(sponsorSegment.endTime)%60];
|
|
self.hud.label.text = formattedManualSkip;
|
|
self.hud.label.numberOfLines = 0;
|
|
[self.hud.button setTitle:LOC(@"Skip") forState:UIControlStateNormal];
|
|
[self.hud.button addTarget:self action:@selector(manuallySkipSegment:) forControlEvents:UIControlEventTouchUpInside];
|
|
// Add custom button to hide HUD
|
|
UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
UIImage *cancelImage = [[UIImage systemImageNamed:@"x.circle"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
|
[cancelButton setImage:cancelImage forState:UIControlStateNormal];
|
|
[cancelButton setTintColor:[[UIColor blackColor] colorWithAlphaComponent:0.7]];
|
|
[cancelButton addTarget:self action:@selector(cancelHUD:) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
UIView *buttonSuperview = self.hud.button.superview;
|
|
[buttonSuperview addSubview:cancelButton];
|
|
|
|
CGFloat buttonSpacing = 10.0;
|
|
cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[cancelButton.topAnchor constraintEqualToAnchor:self.hud.button.topAnchor],
|
|
[cancelButton.leadingAnchor constraintEqualToAnchor:self.hud.button.trailingAnchor constant:buttonSpacing],
|
|
[cancelButton.heightAnchor constraintEqualToAnchor:self.hud.button.heightAnchor]
|
|
]];
|
|
self.hud.offset = CGPointMake(self.view.frame.size.width, -MBProgressMaxOffset);
|
|
|
|
// Use a delay equal to the length of the sponsored segment to avoid HUD call
|
|
double delayInSeconds = sponsorSegment.endTime - sponsorSegment.startTime;
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
[MBProgressHUD hideHUDForView:self.view animated:YES]; // Hide HUD if user is not interacting with buttons
|
|
self.hudDisplayed = NO; // Reset flag to make it work for the next segment
|
|
});
|
|
}
|
|
}
|
|
//edge case where segment end time is longer than the video
|
|
else if (sponsorSegment.endTime > self.currentVideoTotalMediaTime) {
|
|
[self isb_scrubToTime:self.currentVideoTotalMediaTime];
|
|
if (kEnableSkipCountTracking) [SponsorBlockRequest viewedVideoSponsorTime:sponsorSegment];
|
|
}
|
|
else {
|
|
[self isb_scrubToTime:sponsorSegment.endTime];
|
|
if (kEnableSkipCountTracking) [SponsorBlockRequest viewedVideoSponsorTime:sponsorSegment];
|
|
}
|
|
if ([[kCategorySettings objectForKey:sponsorSegment.category] intValue] == 1) {
|
|
if (self.hud.superview != self.view && kShowSkipNotice) {
|
|
[MBProgressHUD hideHUDForView:self.view animated:YES];
|
|
self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
|
|
self.hud.mode = MBProgressHUDModeCustomView;
|
|
// Translate and add segment name to the skipped HUD (issue #70)
|
|
NSString *localizedSegment = categoryLocalization[sponsorSegment.category] ?: sponsorSegment.category;
|
|
self.hud.label.text = [NSString stringWithFormat:LOC(@"SkippedSegment"), localizedSegment];
|
|
self.hud.label.numberOfLines = 0;
|
|
[self.hud.button setTitle:LOC(@"Unskip") forState:UIControlStateNormal];
|
|
[self.hud.button addTarget:self action:@selector(unskipSegment:) forControlEvents:UIControlEventTouchUpInside];
|
|
self.hud.offset = CGPointMake(self.view.frame.size.width, -MBProgressMaxOffset);
|
|
[self.hud hideAnimated:YES afterDelay:kSkipNoticeDuration];
|
|
|
|
// Play sound effect if option enabled
|
|
if (kSkipAudioNotification) {
|
|
playSponsorAudio();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self.currentSponsorSegment <= self.skipSegments.count-1 && [[kCategorySettings objectForKey:sponsorSegment.category] intValue] != 3) self.currentSponsorSegment ++;
|
|
}
|
|
else if (lroundf(arg2.time) > sponsorSegment.startTime && self.currentSponsorSegment != self.skipSegments.count && self.currentSponsorSegment != self.skipSegments.count-1) {
|
|
self.currentSponsorSegment ++;
|
|
}
|
|
else if (self.currentSponsorSegment == 0 && self.unskippedSegment != -1) {
|
|
self.currentSponsorSegment ++;
|
|
}
|
|
else if (self.currentSponsorSegment > 0 && lroundf(arg2.time) < self.skipSegments[self.currentSponsorSegment-1].startTime-0.01) {
|
|
if ([self isMDXActive]) {
|
|
|
|
}
|
|
else if (self.unskippedSegment != self.currentSponsorSegment-1) {
|
|
self.currentSponsorSegment--;
|
|
}
|
|
else if (arg2.time < self.skipSegments[self.currentSponsorSegment-1].startTime-0.01) {
|
|
self.unskippedSegment = -1;
|
|
}
|
|
}
|
|
}
|
|
if ([overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
|
|
YTSegmentableInlinePlayerBarView *playerBarView = overlayView.playerBar.segmentablePlayerBar;
|
|
|
|
[playerBarView maybeCreateMarkerViewsISB];
|
|
|
|
for (UIView *markerView in playerBarView.subviews) {
|
|
if (![playerBarView.sponsorMarkerViews containsObject:markerView] && playerBarView.skipSegments.count == 0) {
|
|
[playerBarView maybeCreateMarkerViewsISB];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- (void)playbackController:(id)arg1 didActivateVideo:(id)arg2 withPlaybackData:(id)arg3 {
|
|
%orig;
|
|
if (self.isPlayingAd) return;
|
|
YTPlayerView *playerView = (YTPlayerView *)self.view;
|
|
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
|
|
if ([overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
|
|
[MBProgressHUD hideHUDForView:playerView animated:YES]; //fix manual skip popup not disappearing when changing videos
|
|
self.hudDisplayed = NO; // Reset flag when changing videos
|
|
|
|
self.skipSegments = [NSMutableArray array];
|
|
self.userSkipSegments = [NSMutableArray array];
|
|
[SponsorBlockRequest getSponsorTimes:self.currentVideoID completionTarget:self completionSelector:@selector(setSkipSegments:) apiInstance:kAPIInstance];
|
|
self.currentSponsorSegment = 0;
|
|
self.unskippedSegment = -1;
|
|
overlayView.controlsOverlayView.playerViewController = self;
|
|
overlayView.controlsOverlayView.isDisplayingSponsorBlockViewController = NO;
|
|
|
|
YTSingleVideoController *activeVideo = self.activeVideo;
|
|
if ([activeVideo isKindOfClass:%c(YTSingleVideoController)]) {
|
|
if ([self.activeVideo.singleVideo respondsToSelector:@selector(video)]) self.channelID = self.activeVideo.singleVideo.video.videoDetails.channelId;
|
|
else self.channelID = self.activeVideo.singleVideo.playbackData.video.videoDetails.channelId;
|
|
}
|
|
}
|
|
}
|
|
- (void)setSkipSegments:(NSMutableArray <SponsorSegment *> *)arg1 {
|
|
%orig;
|
|
NSInteger totalSavedTime = 0;
|
|
for (SponsorSegment *segment in arg1) totalSavedTime += lroundf(segment.endTime) - lroundf(segment.startTime);
|
|
if (arg1.count > 0) {
|
|
NSInteger seconds = lroundf(self.currentVideoTotalMediaTime - totalSavedTime);
|
|
NSInteger hours = seconds / 3600;
|
|
NSInteger minutes = (seconds - (hours * 3600)) / 60;
|
|
seconds = seconds % 60;
|
|
|
|
if (hours >= 1) modifiedTimeString = [NSString stringWithFormat:@"%ld:%02ld:%02ld",hours, minutes, seconds];
|
|
else modifiedTimeString = [NSString stringWithFormat:@"%ld:%02ld", minutes, seconds];
|
|
}
|
|
|
|
else {
|
|
modifiedTimeString = nil;
|
|
}
|
|
}
|
|
|
|
%new
|
|
- (void)isb_fixVisualGlitch {
|
|
if (!self.isPlayingAd) {
|
|
YTPlayerView *playerView = (YTPlayerView *)self.view;
|
|
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
|
|
if ([overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
|
|
YTSegmentableInlinePlayerBarView *playerBarView = overlayView.playerBar.segmentablePlayerBar;
|
|
[playerBarView maybeCreateMarkerViewsISB];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)scrubToTime:(CGFloat)arg1 {
|
|
%orig;
|
|
[self isb_fixVisualGlitch];
|
|
}
|
|
|
|
- (void)seekToTime:(CGFloat)arg1 {
|
|
%orig;
|
|
[self isb_fixVisualGlitch];
|
|
}
|
|
|
|
%new
|
|
- (void)unskipSegment:(UIButton *)sender {
|
|
if (self.currentSponsorSegment > 0) {
|
|
[self isb_scrubToTime:self.skipSegments[self.currentSponsorSegment-1].startTime];
|
|
self.unskippedSegment = self.currentSponsorSegment-1;
|
|
} else {
|
|
[self isb_scrubToTime:self.skipSegments[self.currentSponsorSegment].startTime];
|
|
self.unskippedSegment = self.currentSponsorSegment;
|
|
}
|
|
[MBProgressHUD hideHUDForView:self.view animated:YES];
|
|
}
|
|
|
|
%new
|
|
- (void)manuallySkipSegment:(UIButton *)sender {
|
|
SponsorSegment *sponsorSegment = [[SponsorSegment alloc] initWithStartTime:-1 endTime:-1 category:nil UUID:nil];
|
|
if (self.currentSponsorSegment <= self.skipSegments.count-1) {
|
|
sponsorSegment = self.skipSegments[self.currentSponsorSegment];
|
|
} else if (self.unskippedSegment != self.currentSponsorSegment-1) {
|
|
sponsorSegment = self.skipSegments[self.currentSponsorSegment-1];
|
|
}
|
|
|
|
if (sponsorSegment.endTime > self.currentVideoTotalMediaTime) {
|
|
[self isb_scrubToTime:self.currentVideoTotalMediaTime];
|
|
if (kEnableSkipCountTracking) [SponsorBlockRequest viewedVideoSponsorTime:sponsorSegment];
|
|
}
|
|
else {
|
|
[self isb_scrubToTime:sponsorSegment.endTime];
|
|
if (kEnableSkipCountTracking) [SponsorBlockRequest viewedVideoSponsorTime:sponsorSegment];
|
|
}
|
|
[MBProgressHUD hideHUDForView:self.view animated:YES];
|
|
// Prevent app crashing if segment was already skipped once
|
|
if (self.currentSponsorSegment < 0) {
|
|
self.currentSponsorSegment++;
|
|
}
|
|
|
|
// Reset flag immediately if segment was skipped
|
|
if (self.hudDisplayed != NO) {
|
|
self.hudDisplayed = NO;
|
|
}
|
|
|
|
// Play sound effect if option enabled
|
|
if (kSkipAudioNotification) {
|
|
playSponsorAudio();
|
|
}
|
|
}
|
|
|
|
%new
|
|
- (void)cancelHUD:(UIButton *)sender {
|
|
[MBProgressHUD hideHUDForView:self.view animated:YES];
|
|
}
|
|
|
|
- (void)setPlayerViewLayout:(NSInteger)arg1 {
|
|
%orig;
|
|
YTPlayerView *playerView = (YTPlayerView *)self.view;
|
|
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
|
|
if ([overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
|
|
YTSegmentableInlinePlayerBarView *playerBarView = overlayView.playerBar.segmentablePlayerBar;
|
|
[playerBarView maybeCreateMarkerViewsISB];
|
|
}
|
|
}
|
|
|
|
- (void)updateViewportSizeProvider {
|
|
%orig;
|
|
YTPlayerView *playerView = (YTPlayerView *)self.view;
|
|
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
|
|
if ([overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
|
|
YTSegmentableInlinePlayerBarView *playerBarView = overlayView.playerBar.segmentablePlayerBar;
|
|
[playerBarView maybeCreateMarkerViewsISB];
|
|
}
|
|
}
|
|
%end
|
|
|
|
%hook YTMainAppVideoPlayerOverlayViewController
|
|
|
|
- (void)updateTopRightButtonAvailability {
|
|
%orig;
|
|
YTMainAppVideoPlayerOverlayView *v = [self videoPlayerOverlayView];
|
|
YTMainAppControlsOverlayView *c = [v valueForKey:@"_controlsOverlayView"];
|
|
c.sponsorBlockButton.hidden = !kShowButtonsInPlayer;
|
|
c.sponsorStartedEndedButton.hidden = !kShowButtonsInPlayer || kHideStartEndButtonInPlayer;
|
|
[c setNeedsLayout];
|
|
}
|
|
|
|
%end
|
|
|
|
%hook YTMainAppControlsOverlayView
|
|
%property (retain, nonatomic) YTQTMButton *sponsorBlockButton;
|
|
%property (retain, nonatomic) YTQTMButton *sponsorStartedEndedButton;
|
|
%property (retain, nonatomic) YTPlayerViewController *playerViewController;
|
|
%property (nonatomic, assign) BOOL isDisplayingSponsorBlockViewController;
|
|
|
|
- (id)initWithDelegate:(id)delegate {
|
|
self = %orig;
|
|
if (kShowButtonsInPlayer) {
|
|
CGFloat padding = [[self class] topButtonAdditionalPadding];
|
|
self.sponsorBlockButton = [self buttonWithImage:[UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"PlayerInfoIconSponsorBlocker256px-20@2x" ofType:@"png"]] accessibilityLabel:@"iSponsorBlock" verticalContentPadding:padding];
|
|
[self.sponsorBlockButton addTarget:self action:@selector(sponsorBlockButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
|
self.sponsorBlockButton.hidden = YES;
|
|
self.sponsorBlockButton.alpha = 0;
|
|
|
|
if (!kHideStartEndButtonInPlayer) {
|
|
BOOL isStart = self.playerViewController.userSkipSegments.lastObject.endTime != -1;
|
|
NSString *startedEndedImagePath = isStart ? [tweakBundle pathForResource:@"sponsorblockstart-20@2x" ofType:@"png"] : [tweakBundle pathForResource:@"sponsorblockend-20@2x" ofType:@"png"];
|
|
self.sponsorStartedEndedButton = [self buttonWithImage:[UIImage imageWithContentsOfFile:startedEndedImagePath] accessibilityLabel:isStart ? @"iSponsorBlock start" : @"iSponsorBlock end" verticalContentPadding:padding];
|
|
[self.sponsorStartedEndedButton addTarget:self action:@selector(sponsorStartedEndedButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
|
self.sponsorStartedEndedButton.hidden = YES;
|
|
self.sponsorStartedEndedButton.alpha = 0;
|
|
}
|
|
|
|
@try {
|
|
UIView *containerView = [self valueForKey:@"_topControlsAccessibilityContainerView"];
|
|
[containerView addSubview:self.sponsorBlockButton];
|
|
if (!kHideStartEndButtonInPlayer) {
|
|
[containerView addSubview:self.sponsorStartedEndedButton];
|
|
}
|
|
} @catch (id ex) {
|
|
[self addSubview:self.sponsorBlockButton];
|
|
if (!kHideStartEndButtonInPlayer) {
|
|
[self addSubview:self.sponsorStartedEndedButton];
|
|
}
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (NSMutableArray *)topControls {
|
|
NSMutableArray <UIView *> *topControls = %orig;
|
|
if (kShowButtonsInPlayer) {
|
|
[topControls insertObject:self.sponsorBlockButton atIndex:0];
|
|
if (!kHideStartEndButtonInPlayer) {
|
|
[topControls insertObject:self.sponsorStartedEndedButton atIndex:0];
|
|
}
|
|
}
|
|
return topControls;
|
|
}
|
|
|
|
- (void)setTopOverlayVisible:(BOOL)visible isAutonavCanceledState:(BOOL)canceledState {
|
|
if (self.isDisplayingSponsorBlockViewController) {
|
|
%orig(NO, canceledState);
|
|
self.sponsorBlockButton.imageView.hidden = YES;
|
|
self.sponsorStartedEndedButton.imageView.hidden = YES;
|
|
return;
|
|
}
|
|
|
|
self.sponsorBlockButton.alpha = canceledState || !visible ? 0:1;
|
|
self.sponsorStartedEndedButton.alpha = canceledState || !visible ? 0:1;
|
|
%orig;
|
|
}
|
|
|
|
%new
|
|
- (void)sponsorBlockButtonPressed:(YTQTMButton *)sender {
|
|
self.isDisplayingSponsorBlockViewController = YES;
|
|
self.sponsorBlockButton.hidden = YES;
|
|
self.sponsorStartedEndedButton.hidden = YES;
|
|
if ([self.playerViewController playerViewLayout] == 3) [self.playerViewController didPressToggleFullscreen];
|
|
[self presentSponsorBlockViewController];
|
|
}
|
|
%new
|
|
- (void)sponsorStartedEndedButtonPressed:(YTQTMButton *)sender {
|
|
if (self.playerViewController.userSkipSegments.lastObject.endTime != -1) {
|
|
[self.playerViewController.userSkipSegments addObject:[[SponsorSegment alloc] initWithStartTime:self.playerViewController.currentVideoMediaTime endTime:-1 category:nil UUID:nil]];
|
|
[self.sponsorStartedEndedButton setImage:[UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"sponsorblockend-20@2x" ofType:@"png"]] forState:UIControlStateNormal];
|
|
}
|
|
else {
|
|
self.playerViewController.userSkipSegments.lastObject.endTime = self.playerViewController.currentVideoMediaTime;
|
|
if (self.playerViewController.userSkipSegments.lastObject.endTime != self.playerViewController.currentVideoMediaTime) {
|
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:[NSString stringWithFormat:@"End Time That You Set Was Less Than the Start Time, Please Select a Time After %ld:%02ld",lroundf(self.playerViewController.userSkipSegments.lastObject.startTime)/60, lroundf(self.playerViewController.userSkipSegments.lastObject.startTime)%60] preferredStyle:UIAlertControllerStyleAlert];
|
|
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction * action) {}];
|
|
[alert addAction:defaultAction];
|
|
[[[UIApplication sharedApplication] delegate].window.rootViewController presentViewController:alert animated:YES completion:nil];
|
|
return;
|
|
}
|
|
[self.sponsorStartedEndedButton setImage:[UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"sponsorblockstart-20@2x" ofType:@"png"]] forState:UIControlStateNormal];
|
|
}
|
|
}
|
|
%new
|
|
- (void)presentSponsorBlockViewController {
|
|
SponsorBlockViewController *addSponsorViewController = [[SponsorBlockViewController alloc] init];
|
|
addSponsorViewController.playerViewController = self.playerViewController;
|
|
addSponsorViewController.previousParentViewController = self.playerViewController.parentViewController;
|
|
addSponsorViewController.overlayView = self;
|
|
addSponsorViewController.preferredContentSize = CGSizeMake(CGRectGetWidth(self.playerViewController.view.frame), 0.9 * CGRectGetHeight(UIScreen.mainScreen.bounds));
|
|
[[[UIApplication sharedApplication] delegate].window.rootViewController presentViewController:addSponsorViewController animated:YES completion:nil];
|
|
self.isDisplayingSponsorBlockViewController = YES;
|
|
[self setOverlayVisible:NO];
|
|
|
|
}
|
|
%end
|
|
|
|
%hook YTInlinePlayerBarView
|
|
%property (strong, nonatomic) NSMutableArray *sponsorMarkerViews;
|
|
%property (strong, nonatomic) NSMutableArray *skipSegments;
|
|
%property (strong, nonatomic) YTPlayerViewController *playerViewController;
|
|
%new
|
|
- (void)maybeCreateMarkerViewsISB {
|
|
[self removeSponsorMarkers];
|
|
self.skipSegments = self.skipSegments;
|
|
}
|
|
- (void)setSkipSegments:(NSMutableArray <SponsorSegment *> *)arg1 {
|
|
%orig;
|
|
[self removeSponsorMarkers];
|
|
if ([kWhitelistedChannels containsObject:self.playerViewController.channelID]) {
|
|
return;
|
|
}
|
|
self.sponsorMarkerViews = [NSMutableArray array];
|
|
for (SponsorSegment *segment in arg1) {
|
|
CGFloat startTime = segment.startTime;
|
|
CGFloat endTime = segment.endTime;
|
|
CGFloat beginX = (startTime * self.frame.size.width) / self.totalTime;
|
|
CGFloat endX = (endTime * self.frame.size.width) / self.totalTime;
|
|
CGFloat markerWidth = MAX(endX - beginX, 0);
|
|
|
|
UIColor *color;
|
|
if ([segment.category isEqualToString:@"sponsor"]) color = colorWithHexString([kCategorySettings objectForKey:@"sponsorColor"]);
|
|
else if ([segment.category isEqualToString:@"intro"]) color = colorWithHexString([kCategorySettings objectForKey:@"introColor"]);
|
|
else if ([segment.category isEqualToString:@"outro"]) color = colorWithHexString([kCategorySettings objectForKey:@"outroColor"]);
|
|
else if ([segment.category isEqualToString:@"interaction"]) color = colorWithHexString([kCategorySettings objectForKey:@"interactionColor"]);
|
|
else if ([segment.category isEqualToString:@"selfpromo"]) color = colorWithHexString([kCategorySettings objectForKey:@"selfpromoColor"]);
|
|
else if ([segment.category isEqualToString:@"music_offtopic"]) color = colorWithHexString([kCategorySettings objectForKey:@"music_offtopicColor"]);
|
|
UIView *newMarkerView = [[UIView alloc] initWithFrame:CGRectZero];
|
|
newMarkerView.backgroundColor = color;
|
|
[self addSubview:newMarkerView];
|
|
newMarkerView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
if (isnan(markerWidth) || !isfinite(beginX)) {
|
|
return;
|
|
}
|
|
[newMarkerView.widthAnchor constraintEqualToConstant:markerWidth].active = YES;
|
|
[newMarkerView.heightAnchor constraintEqualToConstant:2].active = YES;
|
|
[newMarkerView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:beginX].active = YES;
|
|
[newMarkerView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
|
|
|
|
[self.sponsorMarkerViews addObject:newMarkerView];
|
|
}
|
|
}
|
|
|
|
%new
|
|
- (void)removeSponsorMarkers {
|
|
for (UIView *markerView in self.sponsorMarkerViews) {
|
|
[markerView removeFromSuperview];
|
|
}
|
|
self.sponsorMarkerViews = [NSMutableArray array];
|
|
}
|
|
%end
|
|
|
|
%hook YTSegmentableInlinePlayerBarView
|
|
%property (strong, nonatomic) NSMutableArray *sponsorMarkerViews;
|
|
%property (strong, nonatomic) NSMutableArray *skipSegments;
|
|
%property (strong, nonatomic) YTPlayerViewController *playerViewController;
|
|
%new
|
|
- (void)maybeCreateMarkerViewsISB {
|
|
[self removeSponsorMarkers];
|
|
self.skipSegments = self.skipSegments;
|
|
}
|
|
- (void)setSkipSegments:(NSMutableArray <SponsorSegment *> *)arg1 {
|
|
%orig;
|
|
[self removeSponsorMarkers];
|
|
if ([kWhitelistedChannels containsObject:self.playerViewController.channelID]) {
|
|
return;
|
|
}
|
|
self.sponsorMarkerViews = [NSMutableArray array];
|
|
UIView *scrubber = [self valueForKey:@"_scrubberCircle"];
|
|
UIView *referenceView = [[self valueForKey:@"_segmentViews"] firstObject];
|
|
if (referenceView == nil) return;
|
|
CGFloat originY = referenceView.frame.origin.y;
|
|
for (SponsorSegment *segment in arg1) {
|
|
CGFloat startTime = segment.startTime;
|
|
CGFloat endTime = segment.endTime;
|
|
CGFloat beginX = (startTime * self.frame.size.width) / self.totalTime;
|
|
CGFloat endX = (endTime * self.frame.size.width) / self.totalTime;
|
|
CGFloat markerWidth = MAX(endX - beginX, 0);
|
|
|
|
UIColor *color;
|
|
if ([segment.category isEqualToString:@"sponsor"]) color = colorWithHexString([kCategorySettings objectForKey:@"sponsorColor"]);
|
|
else if ([segment.category isEqualToString:@"intro"]) color = colorWithHexString([kCategorySettings objectForKey:@"introColor"]);
|
|
else if ([segment.category isEqualToString:@"outro"]) color = colorWithHexString([kCategorySettings objectForKey:@"outroColor"]);
|
|
else if ([segment.category isEqualToString:@"interaction"]) color = colorWithHexString([kCategorySettings objectForKey:@"interactionColor"]);
|
|
else if ([segment.category isEqualToString:@"selfpromo"]) color = colorWithHexString([kCategorySettings objectForKey:@"selfpromoColor"]);
|
|
else if ([segment.category isEqualToString:@"music_offtopic"]) color = colorWithHexString([kCategorySettings objectForKey:@"music_offtopicColor"]);
|
|
|
|
if (isnan(markerWidth) || !isfinite(beginX)) {
|
|
return;
|
|
}
|
|
|
|
UIView *newMarkerView = [[UIView alloc] initWithFrame:CGRectMake(beginX, originY, markerWidth, 2)];
|
|
newMarkerView.userInteractionEnabled = NO;
|
|
newMarkerView.backgroundColor = color;
|
|
[self insertSubview:newMarkerView belowSubview:scrubber];
|
|
[self.sponsorMarkerViews addObject:newMarkerView];
|
|
}
|
|
}
|
|
|
|
%new
|
|
- (void)removeSponsorMarkers {
|
|
for (UIView *markerView in self.sponsorMarkerViews) {
|
|
[markerView removeFromSuperview];
|
|
}
|
|
self.sponsorMarkerViews = [NSMutableArray array];
|
|
}
|
|
%end
|
|
|
|
|
|
%hook YTInlinePlayerBarContainerView
|
|
- (instancetype)initWithScrubbedTimeLabelsDisplayBelowStoryboard:(BOOL)arg1 enableSegmentedProgressView:(BOOL)arg2 {
|
|
return %orig(arg1, YES);
|
|
}
|
|
//does the same thing as the method above on youtube v. 16.0x
|
|
- (instancetype)initWithEnableSegmentedProgressView:(BOOL)arg1 {
|
|
return %orig(YES);
|
|
}
|
|
- (BOOL)alwaysEnableSegmentedProgressView {
|
|
return YES;
|
|
}
|
|
|
|
- (void)setPeekableViewVisible:(BOOL)arg1 {
|
|
%orig;
|
|
if (kShowModifiedTime && modifiedTimeString && ![self.durationLabel.text containsString:modifiedTimeString]) {
|
|
NSString *text = [NSString stringWithFormat:@"%@ (%@)", self.durationLabel.text, modifiedTimeString];
|
|
self.durationLabel.text = text;
|
|
[self.durationLabel sizeToFit];
|
|
}
|
|
}
|
|
|
|
//thanks @iCraze >>
|
|
%new
|
|
- (id)playerBar {
|
|
return [self segmentablePlayerBar];
|
|
}
|
|
%end
|
|
|
|
%hook YTNGWatchLayerViewController
|
|
|
|
- (void)didCompleteFullscreenDismissAnimation {
|
|
%orig;
|
|
YTPlayerView *playerView = (YTPlayerView *)self.playerViewController.view;
|
|
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
|
|
if (!self.playerViewController.isPlayingAd && overlayView.controlsOverlayView.isDisplayingSponsorBlockViewController && [overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
|
|
[overlayView.controlsOverlayView presentSponsorBlockViewController];
|
|
}
|
|
}
|
|
%end
|
|
|
|
//For newer versions of YT the class name changed
|
|
%hook YTWatchLayerViewController
|
|
|
|
- (void)didCompleteFullscreenDismissAnimation {
|
|
%orig;
|
|
YTPlayerView *playerView = (YTPlayerView *)self.playerViewController.view;
|
|
YTMainAppVideoPlayerOverlayView *overlayView = (YTMainAppVideoPlayerOverlayView *)playerView.overlayView;
|
|
if (!self.playerViewController.isPlayingAd && overlayView.controlsOverlayView.isDisplayingSponsorBlockViewController && [overlayView isKindOfClass:%c(YTMainAppVideoPlayerOverlayView)]) {
|
|
[overlayView.controlsOverlayView presentSponsorBlockViewController];
|
|
}
|
|
}
|
|
%end
|
|
|
|
|
|
%hook YTPlayerView
|
|
//https://stackoverflow.com/questions/11770743/capturing-touches-on-a-subview-outside-the-frame-of-its-superview-using-hittest
|
|
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
|
if (self.clipsToBounds || self.hidden || self.alpha == 0) {
|
|
return nil;
|
|
}
|
|
|
|
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
|
|
CGPoint subPoint = [subview convertPoint:point fromView:self];
|
|
UIView *result = [subview hitTest:subPoint withEvent:event];
|
|
if (result) return result;
|
|
}
|
|
return nil;
|
|
}
|
|
%end
|
|
%end
|
|
|
|
%group Cercube
|
|
//ew global variables
|
|
NSArray <SponsorSegment *> *skipSegments;
|
|
AVQueuePlayer *queuePlayer;
|
|
|
|
%hook CADownloadObject
|
|
+ (id)modelWithMetadata:(id)arg1 format:(id)arg2 context:(id)arg3 type:(id)arg4 audioOnly:(_Bool)arg5 directory:(id)arg6 {
|
|
CADownloadObject *downloadObject = %orig;
|
|
[SponsorBlockRequest getSponsorTimes:downloadObject.videoId completionTarget:downloadObject completionSelector:@selector(setSkipSegments:) apiInstance:kAPIInstance];
|
|
return downloadObject;
|
|
}
|
|
|
|
%new
|
|
- (void)setSkipSegments:(NSMutableArray <SponsorSegment *> *)skipSegments {
|
|
NSString *path = [self.filePath.stringByDeletingLastPathComponent stringByAppendingPathComponent:[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"plist"]];
|
|
[[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil];
|
|
NSMutableArray *segments = [NSMutableArray array];
|
|
for (SponsorSegment *segment in skipSegments) {
|
|
[segments addObject:@{
|
|
@"startTime" : @(segment.startTime),
|
|
@"endTime" : @(segment.endTime),
|
|
@"category" : segment.category,
|
|
@"UUID" : segment.UUID
|
|
}];
|
|
}
|
|
NSDictionary *dict = @{
|
|
@"skipSegments" : segments
|
|
};
|
|
[dict writeToURL:[NSURL fileURLWithPath:path isDirectory:NO] error:nil];
|
|
}
|
|
%end
|
|
%hook AVPlayerViewController
|
|
- (void)viewDidLoad {
|
|
%orig;
|
|
[(AVQueuePlayer *)self.player setPlayerViewController:self];
|
|
}
|
|
%end
|
|
|
|
%hook AVScrubber
|
|
//this is bad but i don't feel like finding a better way
|
|
- (void)layoutSubviews {
|
|
%orig;
|
|
[queuePlayer updateMarkerViews];
|
|
}
|
|
%end
|
|
|
|
%hook AVQueuePlayer
|
|
%property (strong, nonatomic) NSMutableArray *skipSegments;
|
|
%property (nonatomic, assign) NSInteger currentSponsorSegment;
|
|
%property (strong, nonatomic) MBProgressHUD *hud;
|
|
%property (nonatomic, assign) NSInteger unskippedSegment;
|
|
%property (nonatomic, assign) BOOL isSeeking;
|
|
%property (nonatomic, assign) NSInteger currentPlayerItem;
|
|
%property (strong, nonatomic) id timeObserver;
|
|
%property (strong, nonatomic) AVPlayerViewController *playerViewController;
|
|
%property (strong, nonatomic) NSMutableArray *markerViews;
|
|
%property (nonatomic, assign) BOOL hudDisplayed;
|
|
- (instancetype)initWithItems:(NSArray<AVPlayerItem *> *)items {
|
|
self.currentPlayerItem = 0;
|
|
queuePlayer = self;
|
|
return %orig;
|
|
}
|
|
- (void)seekToTime:(CMTime)time {
|
|
%orig;
|
|
self.isSeeking = YES;
|
|
[NSTimer scheduledTimerWithTimeInterval:1.0f repeats:NO block:^(NSTimer *timer) {
|
|
self.isSeeking = NO;
|
|
}];
|
|
}
|
|
- (void)_itemIsReadyToPlay:(id)arg1 {
|
|
%orig;
|
|
self.isSeeking = NO;
|
|
[self sponsorBlockSetup];
|
|
}
|
|
- (void)_advanceCurrentItemAccordingToFigPlaybackItem:(id)arg1 {
|
|
%orig;
|
|
if (self.currentPlayerItem + 1 < [self items].count) self.currentPlayerItem ++;
|
|
}
|
|
- (void)_removeItem:(id)arg1 {
|
|
%orig;
|
|
[self removeTimeObserver:self.timeObserver];
|
|
self.timeObserver = nil;
|
|
if (self.currentPlayerItem != 0) self.currentPlayerItem --;
|
|
[self sponsorBlockSetup];
|
|
}
|
|
%new
|
|
- (void)updateMarkerViews {
|
|
if (self.skipSegments.count > 0) {
|
|
CGFloat totalTime = [@([self items][self.currentPlayerItem].duration.value) floatValue] / [self items][self.currentPlayerItem].duration.timescale;
|
|
for (UIView *markerView in self.markerViews) {
|
|
AVScrubber *scrubber = self.playerViewController.contentView.playbackControlsView.scrubber;
|
|
CGFloat startTime = self.skipSegments[[self.markerViews indexOfObject:markerView]].startTime;
|
|
CGFloat endTime = self.skipSegments[[self.markerViews indexOfObject:markerView]].endTime;
|
|
CGFloat beginX = (startTime * scrubber.frame.size.width) / totalTime;
|
|
CGFloat endX = (endTime * scrubber.frame.size.width) / totalTime;
|
|
CGFloat markerWidth = endX - beginX;
|
|
markerView.frame = CGRectMake(beginX, scrubber.frame.size.height/2-2, markerWidth, 5);
|
|
}
|
|
}
|
|
}
|
|
%new
|
|
- (void)sponsorBlockSetup {
|
|
if ([self items].count <= 0) return;
|
|
NSString *path = [[[[self items][self.currentPlayerItem] _URL].path stringByDeletingPathExtension] stringByAppendingPathExtension:@"plist"];
|
|
NSDictionary *segmentsDict = [NSDictionary dictionaryWithContentsOfFile:path];
|
|
NSDictionary *segments = [segmentsDict objectForKey:@"skipSegments"];
|
|
self.skipSegments = [NSMutableArray array];
|
|
CGFloat totalTime = [@([self items][self.currentPlayerItem].duration.value) floatValue] / [self items][self.currentPlayerItem].duration.timescale;
|
|
for (UIView *markerView in self.markerViews) {
|
|
[markerView removeFromSuperview];
|
|
}
|
|
self.markerViews = [NSMutableArray array];
|
|
for (NSDictionary *dict in segments) {
|
|
SponsorSegment *segment = [[SponsorSegment alloc] initWithStartTime:[[dict objectForKey:@"startTime"] floatValue] endTime:[[dict objectForKey:@"endTime"] floatValue] category:[dict objectForKey:@"category"] UUID:[dict objectForKey:@"UUID"]];
|
|
[self.skipSegments addObject:segment];
|
|
AVScrubber *scrubber = self.playerViewController.contentView.playbackControlsView.scrubber;
|
|
CGFloat startTime = segment.startTime;
|
|
CGFloat endTime = segment.endTime;
|
|
CGFloat beginX = (startTime * scrubber.frame.size.width) / totalTime;
|
|
CGFloat endX = (endTime * scrubber.frame.size.width) / totalTime;
|
|
CGFloat markerWidth = endX - beginX;
|
|
UIView *markerView = [[UIView alloc] initWithFrame:CGRectMake(beginX, 2, markerWidth, 5)];
|
|
|
|
if ([segment.category isEqualToString:@"sponsor"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"sponsorColor"]);
|
|
else if ([segment.category isEqualToString:@"intro"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"introColor"]);
|
|
else if ([segment.category isEqualToString:@"outro"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"outroColor"]);
|
|
else if ([segment.category isEqualToString:@"interaction"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"interactionColor"]);
|
|
else if ([segment.category isEqualToString:@"selfpromo"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"selfpromoColor"]);
|
|
else if ([segment.category isEqualToString:@"music_offtopic"]) markerView.backgroundColor = colorWithHexString([kCategorySettings objectForKey:@"music_offtopicColor"]);
|
|
[scrubber addSubview:markerView];
|
|
[self.markerViews addObject:markerView];
|
|
}
|
|
skipSegments = self.skipSegments;
|
|
self.currentSponsorSegment = 0;
|
|
self.unskippedSegment = -1;
|
|
CMTime timeInterval = CMTimeMake(1,10);
|
|
__weak AVQueuePlayer *weakSelf = self;
|
|
[self removeTimeObserver:self.timeObserver];
|
|
self.timeObserver = nil;
|
|
|
|
self.timeObserver = [self addPeriodicTimeObserverForInterval:timeInterval queue:nil usingBlock:^(CMTime time) {
|
|
CGFloat timeFloat = [@(time.value) floatValue] / time.timescale;
|
|
if (weakSelf.skipSegments.count > 0) {
|
|
SponsorSegment *sponsorSegment = [[SponsorSegment alloc] initWithStartTime:-1 endTime:-1 category:nil UUID:nil];
|
|
if (weakSelf.currentSponsorSegment <= weakSelf.skipSegments.count-1) {
|
|
sponsorSegment = weakSelf.skipSegments[weakSelf.currentSponsorSegment];
|
|
}
|
|
else if (weakSelf.unskippedSegment != weakSelf.currentSponsorSegment-1) {
|
|
sponsorSegment = weakSelf.skipSegments[weakSelf.currentSponsorSegment-1];
|
|
}
|
|
|
|
if ((lroundf(timeFloat) == ceil(sponsorSegment.startTime) && timeFloat >= sponsorSegment.startTime) || (lroundf(timeFloat) >= ceil(sponsorSegment.startTime) && timeFloat < sponsorSegment.endTime)) {
|
|
if ([[kCategorySettings objectForKey:sponsorSegment.category] intValue] == 3) {
|
|
if (weakSelf.hud.superview != weakSelf.playerViewController.view && weakSelf.hudDisplayed == NO) {
|
|
weakSelf.hud = [MBProgressHUD showHUDAddedTo:weakSelf.playerViewController.view animated:YES];
|
|
weakSelf.hudDisplayed = YES; // Set yes to make sure that HUD is not persistent (Issue #62)
|
|
weakSelf.hud.mode = MBProgressHUDModeCustomView;
|
|
NSString *localizedSegment = categoryLocalization[sponsorSegment.category] ?: sponsorSegment.category;
|
|
NSString *localizedManualSkip = LOC(@"ManuallySkipReminder");
|
|
NSString *formattedManualSkip = [NSString stringWithFormat:localizedManualSkip, localizedSegment, lroundf(sponsorSegment.startTime)/60, lroundf(sponsorSegment.startTime)%60, lroundf(sponsorSegment.endTime)/60, lroundf(sponsorSegment.endTime)%60];
|
|
weakSelf.hud.label.text = formattedManualSkip;
|
|
weakSelf.hud.label.numberOfLines = 0;
|
|
[weakSelf.hud.button setTitle:LOC(@"Skip") forState:UIControlStateNormal];
|
|
[weakSelf.hud.button addTarget:weakSelf action:@selector(manuallySkipSegment:) forControlEvents:UIControlEventTouchUpInside];
|
|
// Add custom button to hide HUD
|
|
UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
UIImage *cancelImage = [[UIImage systemImageNamed:@"x.circle"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
|
[cancelButton setImage:cancelImage forState:UIControlStateNormal];
|
|
[cancelButton setTintColor:[[UIColor blackColor] colorWithAlphaComponent:0.7]];
|
|
[cancelButton addTarget:weakSelf action:@selector(cancelHUD:) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
UIView *buttonSuperview = weakSelf.hud.button.superview;
|
|
[buttonSuperview addSubview:cancelButton];
|
|
|
|
CGFloat buttonSpacing = 10.0;
|
|
cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[cancelButton.topAnchor constraintEqualToAnchor:weakSelf.hud.button.topAnchor],
|
|
[cancelButton.leadingAnchor constraintEqualToAnchor:weakSelf.hud.button.trailingAnchor constant:buttonSpacing],
|
|
[cancelButton.heightAnchor constraintEqualToAnchor:weakSelf.hud.button.heightAnchor]
|
|
]];
|
|
weakSelf.hud.offset = CGPointMake(weakSelf.playerViewController.view.frame.size.width, -MBProgressMaxOffset);
|
|
double delayInSeconds = sponsorSegment.endTime - sponsorSegment.startTime;
|
|
|
|
// Use a delay equal to the length of the sponsored segment to avoid HUD call
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
[MBProgressHUD hideHUDForView:weakSelf.playerViewController.view animated:YES]; // Hide HUD if user is not interacting with buttons
|
|
weakSelf.hudDisplayed = NO; // Reset flag to make it work for the next segment
|
|
});
|
|
}
|
|
}
|
|
|
|
else if (sponsorSegment.endTime > totalTime) {
|
|
[weakSelf seekToTime:CMTimeMake(totalTime,1) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
|
|
}
|
|
else {
|
|
[weakSelf seekToTime:CMTimeMake(sponsorSegment.endTime,1)];
|
|
}
|
|
|
|
if ([[kCategorySettings objectForKey:sponsorSegment.category] intValue] == 1) {
|
|
if (weakSelf.hud.superview != weakSelf.playerViewController.view && kShowSkipNotice) {
|
|
[MBProgressHUD hideHUDForView:weakSelf.playerViewController.view animated:YES];
|
|
weakSelf.hud = [MBProgressHUD showHUDAddedTo:weakSelf.playerViewController.view animated:YES];
|
|
weakSelf.hud.mode = MBProgressHUDModeCustomView;
|
|
// Translate and add segment name to the skipped HUD (issue #70)
|
|
NSString *localizedSegment = categoryLocalization[sponsorSegment.category] ?: sponsorSegment.category;
|
|
weakSelf.hud.label.text = [NSString stringWithFormat:LOC(@"SkippedSegment"), localizedSegment];
|
|
weakSelf.hud.label.numberOfLines = 0;
|
|
[weakSelf.hud.button setTitle:LOC(@"Unskip") forState:UIControlStateNormal];
|
|
[weakSelf.hud.button addTarget:weakSelf action:@selector(unskipSegment:) forControlEvents:UIControlEventTouchUpInside];
|
|
weakSelf.hud.offset = CGPointMake(weakSelf.playerViewController.view.frame.size.width, -MBProgressMaxOffset);
|
|
[weakSelf.hud hideAnimated:YES afterDelay:kSkipNoticeDuration];
|
|
|
|
// Play sound effect if option enabled
|
|
if (kSkipAudioNotification) {
|
|
playSponsorAudio();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (weakSelf.currentSponsorSegment <= weakSelf.skipSegments.count-1) weakSelf.currentSponsorSegment ++;
|
|
}
|
|
else if (lroundf(timeFloat) > sponsorSegment.startTime && weakSelf.currentSponsorSegment < weakSelf.skipSegments.count-1) {
|
|
weakSelf.currentSponsorSegment ++;
|
|
}
|
|
else if (weakSelf.currentSponsorSegment == 0 && weakSelf.unskippedSegment != -1) {
|
|
weakSelf.currentSponsorSegment ++;
|
|
}
|
|
else if (weakSelf.currentSponsorSegment > 0 && lroundf(timeFloat) < weakSelf.skipSegments[weakSelf.currentSponsorSegment-1].startTime-0.01) {
|
|
if (weakSelf.unskippedSegment != weakSelf.currentSponsorSegment-1) {
|
|
weakSelf.currentSponsorSegment--;
|
|
}
|
|
else if (timeFloat < weakSelf.skipSegments[weakSelf.currentSponsorSegment-1].startTime-0.01) {
|
|
weakSelf.unskippedSegment = -1;
|
|
}
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
%new
|
|
- (void)unskipSegment:(UIButton *)sender {
|
|
if (self.currentSponsorSegment > 0) {
|
|
[self seekToTime:CMTimeMake(self.skipSegments[self.currentSponsorSegment-1].startTime,1)];
|
|
self.unskippedSegment = self.currentSponsorSegment-1;
|
|
} else {
|
|
[self seekToTime:CMTimeMake(self.skipSegments[self.currentSponsorSegment].startTime,1)];
|
|
self.unskippedSegment = self.currentSponsorSegment;
|
|
}
|
|
[MBProgressHUD hideHUDForView:self.playerViewController.view animated:YES];
|
|
}
|
|
%end
|
|
%end
|
|
|
|
%group JustSettings
|
|
NSInteger pageStyle = 0;
|
|
%hook YTRightNavigationButtons
|
|
%property (retain, nonatomic) YTQTMButton *sponsorBlockButton;
|
|
- (NSMutableArray *)buttons {
|
|
NSMutableArray *retVal = %orig.mutableCopy;
|
|
[self.sponsorBlockButton removeFromSuperview];
|
|
[self addSubview:self.sponsorBlockButton];
|
|
if (!self.sponsorBlockButton || pageStyle != [%c(YTPageStyleController) pageStyle]) {
|
|
self.sponsorBlockButton = [%c(YTQTMButton) iconButton];
|
|
self.sponsorBlockButton.frame = CGRectMake(0, 0, 40, 40);
|
|
|
|
if ([%c(YTPageStyleController) pageStyle]) { //dark mode
|
|
[self.sponsorBlockButton setImage:[UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"sponsorblocksettings-20@2x" ofType:@"png"]] forState:UIControlStateNormal];
|
|
}
|
|
else { //light mode
|
|
UIImage *image = [UIImage imageWithContentsOfFile:[tweakBundle pathForResource:@"sponsorblocksettings-20@2x" ofType:@"png"]];
|
|
image = [image imageWithTintColor:UIColor.blackColor renderingMode:UIImageRenderingModeAlwaysTemplate];
|
|
[self.sponsorBlockButton setImage:image forState:UIControlStateNormal];
|
|
[self.sponsorBlockButton setTintColor:UIColor.blackColor];
|
|
}
|
|
pageStyle = [%c(YTPageStyleController) pageStyle];
|
|
|
|
[self.sponsorBlockButton addTarget:self action:@selector(sponsorBlockButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
|
|
[retVal insertObject:self.sponsorBlockButton atIndex:0];
|
|
}
|
|
return retVal;
|
|
}
|
|
- (NSMutableArray *)visibleButtons {
|
|
NSMutableArray *retVal = %orig.mutableCopy;
|
|
|
|
//fixes button overlapping yt logo on smaller devices
|
|
[self setLeadingPadding:-10];
|
|
if (self.sponsorBlockButton) {
|
|
[self.sponsorBlockButton removeFromSuperview];
|
|
[self addSubview:self.sponsorBlockButton];
|
|
[retVal insertObject:self.sponsorBlockButton atIndex:0];
|
|
}
|
|
return retVal;
|
|
}
|
|
%new
|
|
- (void)sponsorBlockButtonPressed:(UIButton *)sender {
|
|
SponsorBlockSettingsController *settingsController = [[SponsorBlockSettingsController alloc] init];
|
|
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:settingsController];
|
|
[[[UIApplication sharedApplication] delegate].window.rootViewController presentViewController:navigationController animated:YES completion:nil];
|
|
}
|
|
%end
|
|
%end
|
|
|
|
static void loadPrefs() {
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
NSString *documentsDirectory = [paths objectAtIndex:0];
|
|
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"iSponsorBlock.plist"];
|
|
NSMutableDictionary *settings = [NSMutableDictionary dictionary];
|
|
[settings addEntriesFromDictionary:[NSDictionary dictionaryWithContentsOfFile:path]];
|
|
kIsEnabled = [settings objectForKey:@"enabled"] ? [[settings objectForKey:@"enabled"] boolValue] : YES;
|
|
|
|
kUserID = [settings objectForKey:@"userID"] ? [settings objectForKey:@"userID"] : [[NSUUID UUID] UUIDString];
|
|
// reset to uuid if user set to an empty string
|
|
if ([kUserID isEqualToString:@""]) kUserID = [[NSUUID UUID] UUIDString];
|
|
|
|
kAPIInstance = [settings objectForKey:@"apiInstance"] ? [settings objectForKey:@"apiInstance"] : @"https://sponsor.ajay.app/api";
|
|
// reset to official if user set to an empty string
|
|
if ([kAPIInstance isEqualToString:@""]) kAPIInstance = @"https://sponsor.ajay.app/api";
|
|
|
|
kCategorySettings = [settings objectForKey:@"categorySettings"] ? [settings objectForKey:@"categorySettings"] : @{
|
|
@"sponsor" : @1,
|
|
@"sponsorColor" : hexFromUIColor(UIColor.greenColor),
|
|
@"intro" : @0,
|
|
@"introColor" : hexFromUIColor(UIColor.systemTealColor),
|
|
@"outro" : @0,
|
|
@"outroColor" : hexFromUIColor(UIColor.blueColor),
|
|
@"interaction" : @0,
|
|
@"interactionColor" : hexFromUIColor(UIColor.systemPinkColor),
|
|
@"selfpromo" : @0,
|
|
@"selfpromoColor" : hexFromUIColor(UIColor.yellowColor),
|
|
@"music_offtopic" : @0,
|
|
@"music_offtopicColor" : hexFromUIColor(UIColor.orangeColor)
|
|
};
|
|
kMinimumDuration = [settings objectForKey:@"minimumDuration"] ? [[settings objectForKey:@"minimumDuration"] floatValue] : 0.0f;
|
|
kShowSkipNotice = [settings objectForKey:@"showSkipNotice"] ? [[settings objectForKey:@"showSkipNotice"] boolValue] : YES;
|
|
kShowButtonsInPlayer = [settings objectForKey:@"showButtonsInPlayer"] ? [[settings objectForKey:@"showButtonsInPlayer"] boolValue] : YES;
|
|
kHideStartEndButtonInPlayer = [settings objectForKey:@"hideStartEndButtonInPlayer"] ? [[settings objectForKey:@"hideStartEndButtonInPlayer"] boolValue] : NO;
|
|
kShowModifiedTime = [settings objectForKey:@"showModifiedTime"] ? [[settings objectForKey:@"showModifiedTime"] boolValue] : YES;
|
|
kSkipAudioNotification = [settings objectForKey:@"skipAudioNotification"] ? [[settings objectForKey:@"skipAudioNotification"] boolValue] : NO;
|
|
kEnableSkipCountTracking = [settings objectForKey:@"enableSkipCountTracking"] ? [[settings objectForKey:@"enableSkipCountTracking"] boolValue] : YES;
|
|
kSkipNoticeDuration = [settings objectForKey:@"skipNoticeDuration"] ? [[settings objectForKey:@"skipNoticeDuration"] floatValue] : 3.0f;
|
|
kWhitelistedChannels = [settings objectForKey:@"whitelistedChannels"] ? [(NSArray *)[settings objectForKey:@"whitelistedChannels"] mutableCopy] : [NSMutableArray array];
|
|
|
|
NSDictionary *newSettings = @{
|
|
@"enabled" : @(kIsEnabled),
|
|
@"userID" : kUserID,
|
|
@"apiInstance" : kAPIInstance,
|
|
@"categorySettings" : kCategorySettings,
|
|
@"minimumDuration" : @(kMinimumDuration),
|
|
@"showSkipNotice" : @(kShowSkipNotice),
|
|
@"showButtonsInPlayer" : @(kShowButtonsInPlayer),
|
|
@"hideStartEndButtonInPlayer" : @(kHideStartEndButtonInPlayer),
|
|
@"showModifiedTime" : @(kShowModifiedTime),
|
|
@"skipAudioNotification" : @(kSkipAudioNotification),
|
|
@"enableSkipCountTracking" : @(kEnableSkipCountTracking),
|
|
@"skipNoticeDuration" : @(kSkipNoticeDuration),
|
|
@"whitelistedChannels" : kWhitelistedChannels
|
|
};
|
|
if (![newSettings isEqualToDictionary:settings]) {
|
|
[newSettings writeToURL:[NSURL fileURLWithPath:path isDirectory:NO] error:nil];
|
|
}
|
|
|
|
}
|
|
|
|
%group LateLoad
|
|
|
|
%hook YTAppDelegate
|
|
|
|
- (BOOL)application:(id)arg1 didFinishLaunchingWithOptions:(id)arg2 {
|
|
BOOL orig = %orig;
|
|
loadPrefs();
|
|
if (kIsEnabled) {
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
NSString *documentsDirectory = [paths objectAtIndex:0];
|
|
if (dlopen(ROOT_PATH("/Library/MobileSubstrate/DynamicLibraries/Cercube.dylib"), RTLD_LAZY)) {
|
|
%init(Cercube)
|
|
NSString *downloadsDirectory = [documentsDirectory stringByAppendingPathComponent:@"Carida_Files"];
|
|
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:downloadsDirectory error:nil];
|
|
for (NSString *path in files) {
|
|
if ([path.pathExtension isEqualToString:@"plist"]) {
|
|
NSString *mp4Path = [downloadsDirectory stringByAppendingPathComponent:[[path stringByDeletingPathExtension] stringByAppendingPathExtension:@"mp4"]];
|
|
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:mp4Path];
|
|
if (!fileExists) {
|
|
[[NSFileManager defaultManager] removeItemAtPath:[downloadsDirectory stringByAppendingPathComponent:path] error:nil];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
%init(Main);
|
|
}
|
|
return orig;
|
|
}
|
|
|
|
%end
|
|
|
|
%end
|
|
|
|
static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
|
|
loadPrefs();
|
|
}
|
|
|
|
%ctor {
|
|
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)prefsChanged, CFSTR("com.galacticdev.isponsorblockprefs.changed"), NULL, CFNotificationSuspensionBehaviorCoalesce);
|
|
%init(LateLoad);
|
|
%init(JustSettings);
|
|
}
|
|
|
|
%dtor {
|
|
if (dlopen(ROOT_PATH("/Library/MobileSubstrate/DynamicLibraries/Cercube.dylib"), RTLD_LAZY)) {
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
NSString *documentsDirectory = [paths objectAtIndex:0];
|
|
NSString *downloadsDirectory = [documentsDirectory stringByAppendingPathComponent:@"Carida_Files"];
|
|
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:downloadsDirectory error:nil];
|
|
for (NSString *path in files) {
|
|
if ([path.pathExtension isEqualToString:@"plist"]) {
|
|
NSString *mp4Path = [downloadsDirectory stringByAppendingPathComponent:[[path stringByDeletingPathExtension] stringByAppendingPathExtension:@"mp4"]];
|
|
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:mp4Path];
|
|
if (!fileExists) {
|
|
[[NSFileManager defaultManager] removeItemAtPath:[downloadsDirectory stringByAppendingPathComponent:path] error:nil];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|