-
-
Notifications
You must be signed in to change notification settings - Fork 416
feat(Prime Video): Add Skip ads
patch
#4824
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
Related to feature request: #4676 |
patches/src/main/kotlin/app/revanced/patches/primevideo/ads/Fingerprints.kt
Outdated
Show resolved
Hide resolved
patches/src/main/kotlin/app/revanced/patches/primevideo/ads/SkipAdsPatch.kt
Outdated
Show resolved
Hide resolved
patches/src/main/kotlin/app/revanced/patches/primevideo/ads/SkipAdsPatch.kt
Outdated
Show resolved
Hide resolved
extensions/primevideo/stub/src/main/java/com/amazon/avod/fsm/StateBase.java
Outdated
Show resolved
Hide resolved
Skip ads
patch
For the other player implementations, it's worth checking an Android tablet (Android Studio device emulator). |
@Suppress("unused") | ||
val skipAdsPatch = bytecodePatch( | ||
name = "Skip ads", | ||
description = "Automatically skip video stream ads.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
description = "Automatically skip video stream ads.", | |
description = "Automatically skips video stream ads.", |
) { | ||
compatibleWith("com.amazon.avod.thirdpartyclient"("3.0.403.257")) | ||
|
||
dependsOn(sharedExtensionPatch("primevideo")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the shared extension create a val sharedExtensionPatch = sharedExtensionPatch("primevideo") just like its done for the other apps for consistency.
// We're going to skip all the logic in ServerInsertedAdBreakState.enter(), which plays all the ad clips in this | ||
// ad break. Instead, we'll force the video player to seek over the entire break and reset the state machine. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// We're going to skip all the logic in ServerInsertedAdBreakState.enter(), which plays all the ad clips in this | |
// ad break. Instead, we'll force the video player to seek over the entire break and reset the state machine. | |
// Skip all the logic in ServerInsertedAdBreakState.enter(), which plays all the ad clips in this | |
// ad break. Instead, force the video player to seek over the entire break and reset the state machine. |
} | ||
|
||
android { | ||
namespace = "app.revanced.extension" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this set already by the patches Gradle plugin?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so. This could be removed.
compileSdk = 34 | ||
|
||
defaultConfig { | ||
minSdk = 21 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this also set already by the patches Gradle plugin?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compile sdk is already set and can be removed here.
But minSdk for Prime video is Android 5.0 and not the default Android 6.0
|
||
@SuppressWarnings("unused") | ||
public final class SkipAdsPatch { | ||
public static void ServerInsertedAdBreakState_enter(ServerInsertedAdBreakState state, AdBreakTrigger trigger, VideoPlayer player) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the naming convention we use for extensions is respected here. Please match the style. Something like "enterServerInsertedAdBreakState"
if (trigger.getSeekStartPosition() != null) | ||
// if scrubbing over ad, seek straight to it | ||
player.seekTo(trigger.getSeekTarget().getTotalMilliseconds()); | ||
else | ||
// if naturally entering ad, seek to end of ad break | ||
player.seekTo(player.getCurrentPosition() + adBreak.getDurationExcludingAux().getTotalMilliseconds()); | ||
|
||
// send "end of ads" trigger to state machine so everything doesn't get whacky | ||
state.doTrigger(new SimpleTrigger(AdEnabledPlayerTriggerType.NO_MORE_ADS_SKIP_TRANSITION)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (trigger.getSeekStartPosition() != null) | |
// if scrubbing over ad, seek straight to it | |
player.seekTo(trigger.getSeekTarget().getTotalMilliseconds()); | |
else | |
// if naturally entering ad, seek to end of ad break | |
player.seekTo(player.getCurrentPosition() + adBreak.getDurationExcludingAux().getTotalMilliseconds()); | |
// send "end of ads" trigger to state machine so everything doesn't get whacky | |
state.doTrigger(new SimpleTrigger(AdEnabledPlayerTriggerType.NO_MORE_ADS_SKIP_TRANSITION)); | |
} | |
if (trigger.getSeekStartPosition() != null) | |
// If scrubbing over an ad, seek straight to it. | |
player.seekTo(trigger.getSeekTarget().getTotalMilliseconds()); | |
else | |
// If naturally entering an ad, seek to end the ad break. | |
player.seekTo(player.getCurrentPosition() + adBreak.getDurationExcludingAux().getTotalMilliseconds()); | |
// Send "end of ads" trigger to state machine so everything doesn't get whacky. | |
state.doTrigger(new SimpleTrigger(AdEnabledPlayerTriggerType.NO_MORE_ADS_SKIP_TRANSITION)); | |
} |
// if scrubbing over ad, seek straight to it | ||
player.seekTo(trigger.getSeekTarget().getTotalMilliseconds()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the point of this?
addInstructions( | ||
getPlayerIndex + 2, | ||
""" | ||
invoke-static {p0, p1, v${playerRegister}}, Lapp/revanced/extension/primevideo/ads/SkipAdsPatch;->ServerInsertedAdBreakState_enter(Lcom/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState;Lcom/amazon/avod/media/ads/internal/state/AdBreakTrigger;Lcom/amazon/avod/media/playback/VideoPlayer;)V |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invoke-static {p0, p1, v${playerRegister}}, Lapp/revanced/extension/primevideo/ads/SkipAdsPatch;->ServerInsertedAdBreakState_enter(Lcom/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState;Lcom/amazon/avod/media/ads/internal/state/AdBreakTrigger;Lcom/amazon/avod/media/playback/VideoPlayer;)V | |
invoke-static { p0, p1, v$playerRegister }, Lapp/revanced/extension/primevideo/ads/SkipAdsPatch;->ServerInsertedAdBreakState_enter(Lcom/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState;Lcom/amazon/avod/media/ads/internal/state/AdBreakTrigger;Lcom/amazon/avod/media/playback/VideoPlayer;)V |
enterServerInsertedAdBreakStateFingerprint.method.apply { | ||
// Get register that stores VideoPlayer: | ||
// invoke-virtual ->getPrimaryPlayer() | ||
// move-result-object {vx} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// move-result-object {vx} | |
// move-result-object { playerRegister } |
Opcode.CONST_4 | ||
) | ||
custom { method, classDef -> | ||
method.name == "enter" && classDef.type == "Lcom/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState;" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is ".type" needed here? ClassDef extends CharSequence
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed since it's not using any String methods (such as endsWith).
but not sure if using == will show a false warning about comparing different types since ClassDef is a CharSequence not a String. I guess it could be with or without .type, but I find it slightly more convoluted comparing the non-String ClassDef object directly to a static String.
This is a first pass of a patch for Amazon Prime Video to skip ads. Ad breaks seem to be baked into the incoming stream, so the best we can do is force the video player to seek over them.
I could use some help with more extensive testing since the Prime Video app has like 5 different video player implementations and I'm not sure when (if?) the ones that I didn't patch are used.