mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2025-05-10 12:45:41 +02:00
feat(Prime Video): Add Skip ads
patch (#4824)
This commit is contained in:
parent
0cf7a4c6be
commit
bb672c4674
23 changed files with 244 additions and 0 deletions
4
extensions/primevideo/build.gradle.kts
Normal file
4
extensions/primevideo/build.gradle.kts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
dependencies {
|
||||||
|
compileOnly(project(":extensions:shared:library"))
|
||||||
|
compileOnly(project(":extensions:primevideo:stub"))
|
||||||
|
}
|
1
extensions/primevideo/src/main/AndroidManifest.xml
Normal file
1
extensions/primevideo/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<manifest/>
|
|
@ -0,0 +1,36 @@
|
||||||
|
package app.revanced.extension.primevideo.ads;
|
||||||
|
|
||||||
|
import com.amazon.avod.fsm.SimpleTrigger;
|
||||||
|
import com.amazon.avod.media.ads.AdBreak;
|
||||||
|
import com.amazon.avod.media.ads.internal.state.AdBreakTrigger;
|
||||||
|
import com.amazon.avod.media.ads.internal.state.AdEnabledPlayerTriggerType;
|
||||||
|
import com.amazon.avod.media.playback.VideoPlayer;
|
||||||
|
import com.amazon.avod.media.ads.internal.state.ServerInsertedAdBreakState;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class SkipAdsPatch {
|
||||||
|
public static void enterServerInsertedAdBreakState(ServerInsertedAdBreakState state, AdBreakTrigger trigger, VideoPlayer player) {
|
||||||
|
try {
|
||||||
|
AdBreak adBreak = trigger.getBreak();
|
||||||
|
|
||||||
|
// There are two scenarios when entering the original method:
|
||||||
|
// 1. Player naturally entered an ad break while watching a video.
|
||||||
|
// 2. User is skipped/scrubbed to a position on the timeline. If seek position is past an ad break,
|
||||||
|
// user is forced to watch an ad before continuing.
|
||||||
|
//
|
||||||
|
// Scenario 2 is indicated by trigger.getSeekStartPosition() != null, so skip directly to the scrubbing
|
||||||
|
// target. Otherwise, just calculate when the ad break should end and skip to there.
|
||||||
|
if (trigger.getSeekStartPosition() != null)
|
||||||
|
player.seekTo(trigger.getSeekTarget().getTotalMilliseconds());
|
||||||
|
else
|
||||||
|
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));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Failed skipping ads", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
extensions/primevideo/stub/build.gradle.kts
Normal file
17
extensions/primevideo/stub/build.gradle.kts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
plugins {
|
||||||
|
id(libs.plugins.android.library.get().pluginId)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
1
extensions/primevideo/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/primevideo/stub/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<manifest/>
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.amazon.avod.fsm;
|
||||||
|
|
||||||
|
public final class SimpleTrigger<T> implements Trigger<T> {
|
||||||
|
public SimpleTrigger(T triggerType) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.amazon.avod.fsm;
|
||||||
|
|
||||||
|
public abstract class StateBase<S, T> {
|
||||||
|
// This method orginally has protected access (modified in patch code).
|
||||||
|
public void doTrigger(Trigger<T> trigger) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.amazon.avod.fsm;
|
||||||
|
|
||||||
|
public interface Trigger<T> {
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.amazon.avod.media;
|
||||||
|
|
||||||
|
public final class TimeSpan {
|
||||||
|
public long getTotalMilliseconds() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.amazon.avod.media.ads;
|
||||||
|
|
||||||
|
import com.amazon.avod.media.TimeSpan;
|
||||||
|
|
||||||
|
public interface AdBreak {
|
||||||
|
TimeSpan getDurationExcludingAux();
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.amazon.avod.media.ads.internal.state;
|
||||||
|
|
||||||
|
public abstract class AdBreakState extends AdEnabledPlaybackState {
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.amazon.avod.media.ads.internal.state;
|
||||||
|
|
||||||
|
import com.amazon.avod.media.ads.AdBreak;
|
||||||
|
import com.amazon.avod.media.TimeSpan;
|
||||||
|
|
||||||
|
public class AdBreakTrigger {
|
||||||
|
public AdBreak getBreak() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan getSeekTarget() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan getSeekStartPosition() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.amazon.avod.media.ads.internal.state;
|
||||||
|
|
||||||
|
import com.amazon.avod.fsm.StateBase;
|
||||||
|
import com.amazon.avod.media.playback.state.PlayerStateType;
|
||||||
|
import com.amazon.avod.media.playback.state.trigger.PlayerTriggerType;
|
||||||
|
|
||||||
|
public class AdEnabledPlaybackState extends StateBase<PlayerStateType, PlayerTriggerType> {
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.amazon.avod.media.ads.internal.state;
|
||||||
|
|
||||||
|
public enum AdEnabledPlayerTriggerType {
|
||||||
|
NO_MORE_ADS_SKIP_TRANSITION
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.amazon.avod.media.ads.internal.state;
|
||||||
|
|
||||||
|
public class ServerInsertedAdBreakState extends AdBreakState {
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.amazon.avod.media.playback;
|
||||||
|
|
||||||
|
public interface VideoPlayer {
|
||||||
|
long getCurrentPosition();
|
||||||
|
|
||||||
|
void seekTo(long positionMs);
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.amazon.avod.media.playback.state;
|
||||||
|
|
||||||
|
public interface PlayerStateType {
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.amazon.avod.media.playback.state.trigger;
|
||||||
|
|
||||||
|
public interface PlayerTriggerType {
|
||||||
|
}
|
|
@ -420,6 +420,14 @@ public final class app/revanced/patches/pixiv/ads/HideAdsPatchKt {
|
||||||
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/primevideo/ads/SkipAdsPatchKt {
|
||||||
|
public static final fun getSkipAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/primevideo/misc/extension/ExtensionPatchKt {
|
||||||
|
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/protonmail/signature/RemoveSentFromSignaturePatchKt {
|
public final class app/revanced/patches/protonmail/signature/RemoveSentFromSignaturePatchKt {
|
||||||
public static final fun getRemoveSentFromSignaturePatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
public static final fun getRemoveSentFromSignaturePatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package app.revanced.patches.primevideo.ads
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
internal val enterServerInsertedAdBreakStateFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC)
|
||||||
|
parameters("Lcom/amazon/avod/fsm/Trigger;")
|
||||||
|
returns("V")
|
||||||
|
opcodes(
|
||||||
|
Opcode.INVOKE_VIRTUAL,
|
||||||
|
Opcode.MOVE_RESULT_OBJECT,
|
||||||
|
Opcode.CONST_4,
|
||||||
|
Opcode.CONST_4
|
||||||
|
)
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "enter" && classDef.type == "Lcom/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val doTriggerFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PROTECTED)
|
||||||
|
returns("V")
|
||||||
|
opcodes(
|
||||||
|
Opcode.IGET_OBJECT,
|
||||||
|
Opcode.INVOKE_INTERFACE,
|
||||||
|
Opcode.RETURN_VOID
|
||||||
|
)
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "doTrigger" && classDef.type == "Lcom/amazon/avod/fsm/StateBase;"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package app.revanced.patches.primevideo.ads
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.primevideo.misc.extension.sharedExtensionPatch
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val skipAdsPatch = bytecodePatch(
|
||||||
|
name = "Skip ads",
|
||||||
|
description = "Automatically skips video stream ads.",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.amazon.avod.thirdpartyclient"("3.0.403.257"))
|
||||||
|
|
||||||
|
dependsOn(sharedExtensionPatch)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
execute {
|
||||||
|
// Force doTrigger() access to public so we can call it from our extension.
|
||||||
|
doTriggerFingerprint.method.accessFlags = AccessFlags.PUBLIC.value;
|
||||||
|
|
||||||
|
val getPlayerIndex = enterServerInsertedAdBreakStateFingerprint.patternMatch!!.startIndex
|
||||||
|
enterServerInsertedAdBreakStateFingerprint.method.apply {
|
||||||
|
// Get register that stores VideoPlayer:
|
||||||
|
// invoke-virtual ->getPrimaryPlayer()
|
||||||
|
// move-result-object { playerRegister }
|
||||||
|
val playerRegister = getInstruction<OneRegisterInstruction>(getPlayerIndex + 1).registerA
|
||||||
|
|
||||||
|
// Reuse the params from the original method:
|
||||||
|
// p0 = ServerInsertedAdBreakState
|
||||||
|
// p1 = AdBreakTrigger
|
||||||
|
addInstructions(
|
||||||
|
getPlayerIndex + 2,
|
||||||
|
"""
|
||||||
|
invoke-static { p0, p1, v$playerRegister }, Lapp/revanced/extension/primevideo/ads/SkipAdsPatch;->enterServerInsertedAdBreakState(Lcom/amazon/avod/media/ads/internal/state/ServerInsertedAdBreakState;Lcom/amazon/avod/media/ads/internal/state/AdBreakTrigger;Lcom/amazon/avod/media/playback/VideoPlayer;)V
|
||||||
|
return-void
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package app.revanced.patches.primevideo.misc.extension
|
||||||
|
|
||||||
|
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||||
|
|
||||||
|
val sharedExtensionPatch = sharedExtensionPatch("primevideo", applicationInitHook)
|
|
@ -0,0 +1,9 @@
|
||||||
|
package app.revanced.patches.primevideo.misc.extension
|
||||||
|
|
||||||
|
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||||
|
|
||||||
|
internal val applicationInitHook = extensionHook {
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "onCreate" && classDef.endsWith("/SplashScreenActivity;")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue