mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-30 12:33:57 -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];
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | 
