,
private val extract: Boolean = true,
private val referer: String? = null,
private val isM3u8: Boolean? = null
@@ -54,7 +47,7 @@ class LinkGenerator(
offset: Int
): Boolean {
links.amap { link ->
- if (!extract || !loadExtractor(link.url, referer, {
+ if (!extract || !loadExtractor(link, referer, {
subtitleCallback(PlayerSubtitleHelper.getSubtitleData(it))
}) {
callback(it to null)
@@ -64,11 +57,11 @@ class LinkGenerator(
callback(
ExtractorLink(
"",
- link.name ?: link.url,
- unshortenLinkSafe(link.url), // unshorten because it might be a raw link
+ link,
+ unshortenLinkSafe(link), // unshorten because it might be a raw link
referer ?: "",
Qualities.Unknown.value, isM3u8 ?: normalSafeApiCall {
- URI(link.url).path?.substringAfterLast(".")?.contains("m3u")
+ URI(link).path?.substringAfterLast(".")?.contains("m3u")
} ?: false
) to null
)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/NonFinalTextRenderer.java b/app/src/main/java/com/lagradost/cloudstream3/ui/player/NonFinalTextRenderer.java
index 3b47b27a..8602ce25 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/NonFinalTextRenderer.java
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/NonFinalTextRenderer.java
@@ -15,8 +15,6 @@
*/
package com.lagradost.cloudstream3.ui.player;
-import static com.google.android.exoplayer2.text.Cue.DIMEN_UNSET;
-import static com.google.android.exoplayer2.text.Cue.LINE_TYPE_NUMBER;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static java.lang.annotation.ElementType.TYPE_USE;
@@ -25,10 +23,8 @@ import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Looper;
import android.os.Message;
-
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
-
import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
@@ -47,17 +43,13 @@ import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
-
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.List;
-import java.util.stream.Collectors;
// DO NOT CONVERT TO KOTLIN AUTOMATICALLY, IT FUCKS UP AND DOES NOT DISPLAY SUBS FOR SOME REASON
-// IF YOU CHANGE THE CODE MAKE SURE YOU GET THE CUES CORRECT!
-
/**
* A renderer for text.
*
@@ -67,390 +59,365 @@ import java.util.stream.Collectors;
*/
public class NonFinalTextRenderer extends BaseRenderer implements Callback {
- private static final String TAG = "TextRenderer";
+ private static final String TAG = "TextRenderer";
- /**
- * @param trackType The track type that the renderer handles. One of the {@link C} {@code
- * TRACK_TYPE_*} constants.
- * @param outputHandler
- */
- public NonFinalTextRenderer(int trackType, @Nullable Handler outputHandler) {
- super(trackType);
- this.outputHandler = outputHandler;
+ /**
+ * @param trackType The track type that the renderer handles. One of the {@link C} {@code
+ * TRACK_TYPE_*} constants.
+ * @param outputHandler
+ */
+ public NonFinalTextRenderer(int trackType, @Nullable Handler outputHandler) {
+ super(trackType);
+ this.outputHandler = outputHandler;
+ }
+
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @Target(TYPE_USE)
+ @IntDef({
+ REPLACEMENT_STATE_NONE,
+ REPLACEMENT_STATE_SIGNAL_END_OF_STREAM,
+ REPLACEMENT_STATE_WAIT_END_OF_STREAM
+ })
+ private @interface ReplacementState {}
+ /** The decoder does not need to be replaced. */
+ private static final int REPLACEMENT_STATE_NONE = 0;
+ /**
+ * The decoder needs to be replaced, but we haven't yet signaled an end of stream to the existing
+ * decoder. We need to do so in order to ensure that it outputs any remaining buffers before we
+ * release it.
+ */
+ private static final int REPLACEMENT_STATE_SIGNAL_END_OF_STREAM = 1;
+ /**
+ * The decoder needs to be replaced, and we've signaled an end of stream to the existing decoder.
+ * We're waiting for the decoder to output an end of stream signal to indicate that it has output
+ * any remaining buffers before we release it.
+ */
+ private static final int REPLACEMENT_STATE_WAIT_END_OF_STREAM = 2;
+
+ private static final int MSG_UPDATE_OUTPUT = 0;
+
+ @Nullable private final Handler outputHandler;
+ private TextOutput output = null;
+ private SubtitleDecoderFactory decoderFactory = null;
+ private FormatHolder formatHolder = null;
+
+ private boolean inputStreamEnded;
+ private boolean outputStreamEnded;
+ private boolean waitingForKeyFrame;
+ private @ReplacementState int decoderReplacementState;
+ @Nullable private Format streamFormat;
+ @Nullable private SubtitleDecoder decoder;
+ @Nullable private SubtitleInputBuffer nextInputBuffer;
+ @Nullable private SubtitleOutputBuffer subtitle;
+ @Nullable private SubtitleOutputBuffer nextSubtitle;
+ private int nextSubtitleEventIndex;
+ private long finalStreamEndPositionUs;
+
+ /**
+ * @param output The output.
+ * @param outputLooper The looper associated with the thread on which the output should be called.
+ * If the output makes use of standard Android UI components, then this should normally be the
+ * looper associated with the application's main thread, which can be obtained using {@link
+ * android.app.Activity#getMainLooper()}. Null may be passed if the output should be called
+ * directly on the player's internal rendering thread.
+ */
+ public NonFinalTextRenderer(TextOutput output, @Nullable Looper outputLooper) {
+ this(output, outputLooper, SubtitleDecoderFactory.DEFAULT);
+ }
+
+ /**
+ * @param output The output.
+ * @param outputLooper The looper associated with the thread on which the output should be called.
+ * If the output makes use of standard Android UI components, then this should normally be the
+ * looper associated with the application's main thread, which can be obtained using {@link
+ * android.app.Activity#getMainLooper()}. Null may be passed if the output should be called
+ * directly on the player's internal rendering thread.
+ * @param decoderFactory A factory from which to obtain {@link SubtitleDecoder} instances.
+ */
+ public NonFinalTextRenderer(
+ TextOutput output, @Nullable Looper outputLooper, SubtitleDecoderFactory decoderFactory) {
+ super(C.TRACK_TYPE_TEXT);
+ this.output = checkNotNull(output);
+ this.outputHandler =
+ outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this);
+ this.decoderFactory = decoderFactory;
+ formatHolder = new FormatHolder();
+ finalStreamEndPositionUs = C.TIME_UNSET;
+ }
+
+ @Override
+ public String getName() {
+ return TAG;
+ }
+
+ @Override
+ public @Capabilities int supportsFormat(Format format) {
+ if (decoderFactory.supportsFormat(format)) {
+ return RendererCapabilities.create(
+ format.cryptoType == C.CRYPTO_TYPE_NONE ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_DRM);
+ } else if (MimeTypes.isText(format.sampleMimeType)) {
+ return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE);
+ } else {
+ return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE);
+ }
+ }
+
+ /**
+ * Sets the position at which to stop rendering the current stream.
+ *
+ * Must be called after {@link #setCurrentStreamFinal()}.
+ *
+ * @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to
+ * render until the end of the current stream.
+ */
+ // TODO(internal b/181312195): Remove this when it's no longer needed once subtitles are decoded
+ // on the loading side of SampleQueue.
+ public void setFinalStreamEndPositionUs(long streamEndPositionUs) {
+ checkState(isCurrentStreamFinal());
+ this.finalStreamEndPositionUs = streamEndPositionUs;
+ }
+
+ @Override
+ protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
+ streamFormat = formats[0];
+ if (decoder != null) {
+ decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM;
+ } else {
+ initDecoder();
+ }
+ }
+
+ @Override
+ protected void onPositionReset(long positionUs, boolean joining) {
+ clearOutput();
+ inputStreamEnded = false;
+ outputStreamEnded = false;
+ finalStreamEndPositionUs = C.TIME_UNSET;
+ if (decoderReplacementState != REPLACEMENT_STATE_NONE) {
+ replaceDecoder();
+ } else {
+ releaseBuffers();
+ checkNotNull(decoder).flush();
+ }
+ }
+
+ @Override
+ public void render(long positionUs, long elapsedRealtimeUs) {
+ if (isCurrentStreamFinal()
+ && finalStreamEndPositionUs != C.TIME_UNSET
+ && positionUs >= finalStreamEndPositionUs) {
+ releaseBuffers();
+ outputStreamEnded = true;
}
- @Documented
- @Retention(RetentionPolicy.SOURCE)
- @Target(TYPE_USE)
- @IntDef({
- REPLACEMENT_STATE_NONE,
- REPLACEMENT_STATE_SIGNAL_END_OF_STREAM,
- REPLACEMENT_STATE_WAIT_END_OF_STREAM
- })
- private @interface ReplacementState {
+ if (outputStreamEnded) {
+ return;
}
- /**
- * The decoder does not need to be replaced.
- */
- private static final int REPLACEMENT_STATE_NONE = 0;
- /**
- * The decoder needs to be replaced, but we haven't yet signaled an end of stream to the existing
- * decoder. We need to do so in order to ensure that it outputs any remaining buffers before we
- * release it.
- */
- private static final int REPLACEMENT_STATE_SIGNAL_END_OF_STREAM = 1;
- /**
- * The decoder needs to be replaced, and we've signaled an end of stream to the existing decoder.
- * We're waiting for the decoder to output an end of stream signal to indicate that it has output
- * any remaining buffers before we release it.
- */
- private static final int REPLACEMENT_STATE_WAIT_END_OF_STREAM = 2;
-
- private static final int MSG_UPDATE_OUTPUT = 0;
-
- @Nullable
- private final Handler outputHandler;
- private TextOutput output = null;
- private SubtitleDecoderFactory decoderFactory = null;
- private FormatHolder formatHolder = null;
-
- private boolean inputStreamEnded;
- private boolean outputStreamEnded;
- private boolean waitingForKeyFrame;
- private @ReplacementState int decoderReplacementState;
- @Nullable
- private Format streamFormat;
- @Nullable
- private SubtitleDecoder decoder;
- @Nullable
- private SubtitleInputBuffer nextInputBuffer;
- @Nullable
- private SubtitleOutputBuffer subtitle;
- @Nullable
- private SubtitleOutputBuffer nextSubtitle;
- private int nextSubtitleEventIndex;
- private long finalStreamEndPositionUs;
-
- /**
- * @param output The output.
- * @param outputLooper The looper associated with the thread on which the output should be called.
- * If the output makes use of standard Android UI components, then this should normally be the
- * looper associated with the application's main thread, which can be obtained using {@link
- * android.app.Activity#getMainLooper()}. Null may be passed if the output should be called
- * directly on the player's internal rendering thread.
- */
- public NonFinalTextRenderer(TextOutput output, @Nullable Looper outputLooper) {
- this(output, outputLooper, SubtitleDecoderFactory.DEFAULT);
+ if (nextSubtitle == null) {
+ checkNotNull(decoder).setPositionUs(positionUs);
+ try {
+ nextSubtitle = checkNotNull(decoder).dequeueOutputBuffer();
+ } catch (SubtitleDecoderException e) {
+ handleDecoderError(e);
+ return;
+ }
}
- /**
- * @param output The output.
- * @param outputLooper The looper associated with the thread on which the output should be called.
- * If the output makes use of standard Android UI components, then this should normally be the
- * looper associated with the application's main thread, which can be obtained using {@link
- * android.app.Activity#getMainLooper()}. Null may be passed if the output should be called
- * directly on the player's internal rendering thread.
- * @param decoderFactory A factory from which to obtain {@link SubtitleDecoder} instances.
- */
- public NonFinalTextRenderer(
- TextOutput output, @Nullable Looper outputLooper, SubtitleDecoderFactory decoderFactory) {
- super(C.TRACK_TYPE_TEXT);
- this.output = checkNotNull(output);
- this.outputHandler =
- outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this);
- this.decoderFactory = decoderFactory;
- formatHolder = new FormatHolder();
- finalStreamEndPositionUs = C.TIME_UNSET;
+ if (getState() != STATE_STARTED) {
+ return;
}
- @Override
- public String getName() {
- return TAG;
+ boolean textRendererNeedsUpdate = false;
+ if (subtitle != null) {
+ // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we
+ // advance to the next event.
+ long subtitleNextEventTimeUs = getNextEventTime();
+ while (subtitleNextEventTimeUs <= positionUs) {
+ nextSubtitleEventIndex++;
+ subtitleNextEventTimeUs = getNextEventTime();
+ textRendererNeedsUpdate = true;
+ }
}
-
- @Override
- public @Capabilities int supportsFormat(Format format) {
- if (decoderFactory.supportsFormat(format)) {
- return RendererCapabilities.create(
- format.cryptoType == C.CRYPTO_TYPE_NONE ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_DRM);
- } else if (MimeTypes.isText(format.sampleMimeType)) {
- return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE);
- } else {
- return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE);
- }
- }
-
- /**
- * Sets the position at which to stop rendering the current stream.
- *
- *
Must be called after {@link #setCurrentStreamFinal()}.
- *
- * @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to
- * render until the end of the current stream.
- */
- // TODO(internal b/181312195): Remove this when it's no longer needed once subtitles are decoded
- // on the loading side of SampleQueue.
- public void setFinalStreamEndPositionUs(long streamEndPositionUs) {
- checkState(isCurrentStreamFinal());
- this.finalStreamEndPositionUs = streamEndPositionUs;
- }
-
- @Override
- protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
- streamFormat = formats[0];
- if (decoder != null) {
- decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM;
- } else {
- initDecoder();
- }
- }
-
- @Override
- protected void onPositionReset(long positionUs, boolean joining) {
- clearOutput();
- inputStreamEnded = false;
- outputStreamEnded = false;
- finalStreamEndPositionUs = C.TIME_UNSET;
- if (decoderReplacementState != REPLACEMENT_STATE_NONE) {
+ if (nextSubtitle != null) {
+ SubtitleOutputBuffer nextSubtitle = this.nextSubtitle;
+ if (nextSubtitle.isEndOfStream()) {
+ if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) {
+ if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) {
replaceDecoder();
- } else {
- releaseBuffers();
- checkNotNull(decoder).flush();
- }
- }
-
- @Override
- public void render(long positionUs, long elapsedRealtimeUs) {
- if (isCurrentStreamFinal()
- && finalStreamEndPositionUs != C.TIME_UNSET
- && positionUs >= finalStreamEndPositionUs) {
+ } else {
releaseBuffers();
outputStreamEnded = true;
+ }
}
-
- if (outputStreamEnded) {
- return;
- }
-
- if (nextSubtitle == null) {
- checkNotNull(decoder).setPositionUs(positionUs);
- try {
- nextSubtitle = checkNotNull(decoder).dequeueOutputBuffer();
- } catch (SubtitleDecoderException e) {
- handleDecoderError(e);
- return;
- }
- }
-
- if (getState() != STATE_STARTED) {
- return;
- }
-
- boolean textRendererNeedsUpdate = false;
+ } else if (nextSubtitle.timeUs <= positionUs) {
+ // Advance to the next subtitle. Sync the next event index and trigger an update.
if (subtitle != null) {
- // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we
- // advance to the next event.
- long subtitleNextEventTimeUs = getNextEventTime();
- while (subtitleNextEventTimeUs <= positionUs) {
- nextSubtitleEventIndex++;
- subtitleNextEventTimeUs = getNextEventTime();
- textRendererNeedsUpdate = true;
- }
- }
- if (nextSubtitle != null) {
- SubtitleOutputBuffer nextSubtitle = this.nextSubtitle;
- if (nextSubtitle.isEndOfStream()) {
- if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) {
- if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) {
- replaceDecoder();
- } else {
- releaseBuffers();
- outputStreamEnded = true;
- }
- }
- } else if (nextSubtitle.timeUs <= positionUs) {
- // Advance to the next subtitle. Sync the next event index and trigger an update.
- if (subtitle != null) {
- subtitle.release();
- }
- nextSubtitleEventIndex = nextSubtitle.getNextEventTimeIndex(positionUs);
- subtitle = nextSubtitle;
- this.nextSubtitle = null;
- textRendererNeedsUpdate = true;
- }
+ subtitle.release();
}
+ nextSubtitleEventIndex = nextSubtitle.getNextEventTimeIndex(positionUs);
+ subtitle = nextSubtitle;
+ this.nextSubtitle = null;
+ textRendererNeedsUpdate = true;
+ }
+ }
- if (textRendererNeedsUpdate) {
- // If textRendererNeedsUpdate then subtitle must be non-null.
- checkNotNull(subtitle);
- // textRendererNeedsUpdate is set and we're playing. Update the renderer.
- updateOutput(subtitle.getCues(positionUs));
- }
+ if (textRendererNeedsUpdate) {
+ // If textRendererNeedsUpdate then subtitle must be non-null.
+ checkNotNull(subtitle);
+ // textRendererNeedsUpdate is set and we're playing. Update the renderer.
+ updateOutput(subtitle.getCues(positionUs));
+ }
- if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) {
+ if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) {
+ return;
+ }
+
+ try {
+ while (!inputStreamEnded) {
+ @Nullable SubtitleInputBuffer nextInputBuffer = this.nextInputBuffer;
+ if (nextInputBuffer == null) {
+ nextInputBuffer = checkNotNull(decoder).dequeueInputBuffer();
+ if (nextInputBuffer == null) {
return;
+ }
+ this.nextInputBuffer = nextInputBuffer;
}
-
- try {
- while (!inputStreamEnded) {
- @Nullable SubtitleInputBuffer nextInputBuffer = this.nextInputBuffer;
- if (nextInputBuffer == null) {
- nextInputBuffer = checkNotNull(decoder).dequeueInputBuffer();
- if (nextInputBuffer == null) {
- return;
- }
- this.nextInputBuffer = nextInputBuffer;
- }
- if (decoderReplacementState == REPLACEMENT_STATE_SIGNAL_END_OF_STREAM) {
- nextInputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
- checkNotNull(decoder).queueInputBuffer(nextInputBuffer);
- this.nextInputBuffer = null;
- decoderReplacementState = REPLACEMENT_STATE_WAIT_END_OF_STREAM;
- return;
- }
- // Try and read the next subtitle from the source.
- @ReadDataResult int result = readSource(formatHolder, nextInputBuffer, /* readFlags= */ 0);
- if (result == C.RESULT_BUFFER_READ) {
- if (nextInputBuffer.isEndOfStream()) {
- inputStreamEnded = true;
- waitingForKeyFrame = false;
- } else {
- @Nullable Format format = formatHolder.format;
- if (format == null) {
- // We haven't received a format yet.
- return;
- }
- nextInputBuffer.subsampleOffsetUs = format.subsampleOffsetUs;
- nextInputBuffer.flip();
- waitingForKeyFrame &= !nextInputBuffer.isKeyFrame();
- }
- if (!waitingForKeyFrame) {
- checkNotNull(decoder).queueInputBuffer(nextInputBuffer);
- this.nextInputBuffer = null;
- }
- } else if (result == C.RESULT_NOTHING_READ) {
- return;
- }
+ if (decoderReplacementState == REPLACEMENT_STATE_SIGNAL_END_OF_STREAM) {
+ nextInputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
+ checkNotNull(decoder).queueInputBuffer(nextInputBuffer);
+ this.nextInputBuffer = null;
+ decoderReplacementState = REPLACEMENT_STATE_WAIT_END_OF_STREAM;
+ return;
+ }
+ // Try and read the next subtitle from the source.
+ @ReadDataResult int result = readSource(formatHolder, nextInputBuffer, /* readFlags= */ 0);
+ if (result == C.RESULT_BUFFER_READ) {
+ if (nextInputBuffer.isEndOfStream()) {
+ inputStreamEnded = true;
+ waitingForKeyFrame = false;
+ } else {
+ @Nullable Format format = formatHolder.format;
+ if (format == null) {
+ // We haven't received a format yet.
+ return;
}
- } catch (SubtitleDecoderException e) {
- handleDecoderError(e);
+ nextInputBuffer.subsampleOffsetUs = format.subsampleOffsetUs;
+ nextInputBuffer.flip();
+ waitingForKeyFrame &= !nextInputBuffer.isKeyFrame();
+ }
+ if (!waitingForKeyFrame) {
+ checkNotNull(decoder).queueInputBuffer(nextInputBuffer);
+ this.nextInputBuffer = null;
+ }
+ } else if (result == C.RESULT_NOTHING_READ) {
+ return;
}
+ }
+ } catch (SubtitleDecoderException e) {
+ handleDecoderError(e);
}
+ }
- @Override
- protected void onDisabled() {
- streamFormat = null;
- finalStreamEndPositionUs = C.TIME_UNSET;
- clearOutput();
- releaseDecoder();
+ @Override
+ protected void onDisabled() {
+ streamFormat = null;
+ finalStreamEndPositionUs = C.TIME_UNSET;
+ clearOutput();
+ releaseDecoder();
+ }
+
+ @Override
+ public boolean isEnded() {
+ return outputStreamEnded;
+ }
+
+ @Override
+ public boolean isReady() {
+ // Don't block playback whilst subtitles are loading.
+ // Note: To change this behavior, it will be necessary to consider [Internal: b/12949941].
+ return true;
+ }
+
+ private void releaseBuffers() {
+ nextInputBuffer = null;
+ nextSubtitleEventIndex = C.INDEX_UNSET;
+ if (subtitle != null) {
+ subtitle.release();
+ subtitle = null;
}
-
- @Override
- public boolean isEnded() {
- return outputStreamEnded;
+ if (nextSubtitle != null) {
+ nextSubtitle.release();
+ nextSubtitle = null;
}
+ }
- @Override
- public boolean isReady() {
- // Don't block playback whilst subtitles are loading.
- // Note: To change this behavior, it will be necessary to consider [Internal: b/12949941].
+ private void releaseDecoder() {
+ releaseBuffers();
+ checkNotNull(decoder).release();
+ decoder = null;
+ decoderReplacementState = REPLACEMENT_STATE_NONE;
+ }
+
+ private void initDecoder() {
+ waitingForKeyFrame = true;
+ decoder = decoderFactory.createDecoder(checkNotNull(streamFormat));
+ }
+
+ private void replaceDecoder() {
+ releaseDecoder();
+ initDecoder();
+ }
+
+ private long getNextEventTime() {
+ if (nextSubtitleEventIndex == C.INDEX_UNSET) {
+ return Long.MAX_VALUE;
+ }
+ checkNotNull(subtitle);
+ return nextSubtitleEventIndex >= subtitle.getEventTimeCount()
+ ? Long.MAX_VALUE
+ : subtitle.getEventTime(nextSubtitleEventIndex);
+ }
+
+ private void updateOutput(List cues) {
+ if (outputHandler != null) {
+ outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget();
+ } else {
+ invokeUpdateOutputInternal(cues);
+ }
+ }
+
+ private void clearOutput() {
+ updateOutput(Collections.emptyList());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_OUTPUT:
+ invokeUpdateOutputInternal((List) msg.obj);
return true;
+ default:
+ throw new IllegalStateException();
}
+ }
- private void releaseBuffers() {
- nextInputBuffer = null;
- nextSubtitleEventIndex = C.INDEX_UNSET;
- if (subtitle != null) {
- subtitle.release();
- subtitle = null;
- }
- if (nextSubtitle != null) {
- nextSubtitle.release();
- nextSubtitle = null;
- }
- }
+ private void invokeUpdateOutputInternal(List cues) {
+ output.onCues(cues);
+ output.onCues(new CueGroup(cues));
+ }
- private void releaseDecoder() {
- releaseBuffers();
- checkNotNull(decoder).release();
- decoder = null;
- decoderReplacementState = REPLACEMENT_STATE_NONE;
- }
-
- private void initDecoder() {
- waitingForKeyFrame = true;
- decoder = decoderFactory.createDecoder(checkNotNull(streamFormat));
- }
-
- private void replaceDecoder() {
- releaseDecoder();
- initDecoder();
- }
-
- private long getNextEventTime() {
- if (nextSubtitleEventIndex == C.INDEX_UNSET) {
- return Long.MAX_VALUE;
- }
- checkNotNull(subtitle);
- return nextSubtitleEventIndex >= subtitle.getEventTimeCount()
- ? Long.MAX_VALUE
- : subtitle.getEventTime(nextSubtitleEventIndex);
- }
-
- private void updateOutput(List cues) {
- if (outputHandler != null) {
- outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget();
- } else {
- invokeUpdateOutputInternal(cues);
- }
- }
-
- private void clearOutput() {
- updateOutput(Collections.emptyList());
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE_OUTPUT:
- invokeUpdateOutputInternal((List) msg.obj);
- return true;
- default:
- throw new IllegalStateException();
- }
- }
-
- private void invokeUpdateOutputInternal(List cues) {
- // See https://github.com/google/ExoPlayer/issues/7934
- // SubripDecoder texts tend to be DIMEN_UNSET which pushes up the
- // subs unlike WEBVTT which creates an inconsistency
-
- List fixedCues = cues.stream().map(
- cue -> {
- Cue.Builder builder = cue.buildUpon();
-
- if (cue.line == DIMEN_UNSET)
- builder.setLine(-1f, LINE_TYPE_NUMBER);
-
- return builder.setSize(DIMEN_UNSET).build();
- }
- ).collect(Collectors.toList());
-
- output.onCues(fixedCues);
- output.onCues(new CueGroup(fixedCues, 0L));
- }
-
- /**
- * Called when {@link #decoder} throws an exception, so it can be logged and playback can
- * continue.
- *
- * Logs {@code e} and resets state to allow decoding the next sample.
- */
- private void handleDecoderError(SubtitleDecoderException e) {
- Log.e(TAG, "Subtitle decoding failed. streamFormat=" + streamFormat, e);
- clearOutput();
- replaceDecoder();
- }
+ /**
+ * Called when {@link #decoder} throws an exception, so it can be logged and playback can
+ * continue.
+ *
+ *
Logs {@code e} and resets state to allow decoding the next sample.
+ */
+ private void handleDecoderError(SubtitleDecoderException e) {
+ Log.e(TAG, "Subtitle decoding failed. streamFormat=" + streamFormat, e);
+ clearOutput();
+ replaceDecoder();
+ }
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt
index 7faf0cf5..4f16e9f6 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt
@@ -35,13 +35,6 @@ class PlayerGeneratorViewModel : ViewModel() {
private val _currentStamps = MutableLiveData>(emptyList())
val currentStamps: LiveData> = _currentStamps
- private val _currentSubtitleYear = MutableLiveData(null)
- val currentSubtitleYear: LiveData = _currentSubtitleYear
-
- fun setSubtitleYear(year: Int?) {
- _currentSubtitleYear.postValue(year)
- }
-
fun getId(): Int? {
return generator?.getCurrentId()
}
@@ -120,9 +113,8 @@ class PlayerGeneratorViewModel : ViewModel() {
// Do not post if there's nothing new
// Posting will refresh subtitles which will in turn
// make the subs to english if previously unselected
- if (allSubs != currentSubs) {
+ if (allSubs != currentSubs)
_currentSubs.postValue(allSubs)
- }
}
private var currentJob: Job? = null
@@ -172,10 +164,9 @@ class PlayerGeneratorViewModel : ViewModel() {
}
_loadingLinks.postValue(loadingState)
+
_currentLinks.postValue(currentLinks)
- _currentSubs.postValue(
- currentSubs.union(_currentSubs.value ?: emptySet())
- )
+ _currentSubs.postValue(currentSubs)
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt
index 8d85f176..b06812c4 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt
@@ -28,7 +28,7 @@ enum class SubtitleOrigin {
/**
* @param name To be displayed in the player
- * @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend id
+ * @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend language
* @param headers if empty it will use the base onlineDataSource headers else only the specified headers
* */
data class SubtitleData(
@@ -37,13 +37,7 @@ data class SubtitleData(
val origin: SubtitleOrigin,
val mimeType: String,
val headers: Map
-) {
- /** Internal ID for exoplayer, unique for each link*/
- fun getId(): String {
- return if (origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) url
- else "$url|$name"
- }
-}
+)
class PlayerSubtitleHelper {
private var activeSubtitles: Set = emptySet()
@@ -85,11 +79,11 @@ class PlayerSubtitleHelper {
}
}
- fun subtitleStatus(sub: SubtitleData?): SubtitleStatus {
- if (activeSubtitles.contains(sub)) {
+ fun subtitleStatus(sub : SubtitleData?): SubtitleStatus {
+ if(activeSubtitles.contains(sub)) {
return SubtitleStatus.IS_ACTIVE
}
- if (allSubtitles.contains(sub)) {
+ if(allSubtitles.contains(sub)) {
return SubtitleStatus.REQUIRES_RELOAD
}
return SubtitleStatus.NOT_FOUND
@@ -101,7 +95,7 @@ class PlayerSubtitleHelper {
regexSubtitlesToRemoveCaptions = style.removeCaptions
subtitleView?.context?.let { ctx ->
subStyle = style
- Log.i(TAG, "SET STYLE = $style")
+ Log.i(TAG,"SET STYLE = $style")
subtitleView?.setStyle(ctx.fromSaveToStyle(style))
subtitleView?.translationY = -style.elevation.toPx.toFloat()
val size = style.fixedTextSize
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt
index ba57d2de..62f967d2 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt
@@ -15,7 +15,6 @@ import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
-import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
import com.lagradost.cloudstream3.APIHolder.filterSearchResultByFilmQuality
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
@@ -31,7 +30,6 @@ import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.ui.search.SearchViewModel
-import com.lagradost.cloudstream3.utils.AppUtils.ownShow
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
@@ -73,8 +71,6 @@ class QuickSearchFragment : Fragment() {
private var providers: Set? = null
private lateinit var searchViewModel: SearchViewModel
- private var bottomSheetDialog: BottomSheetDialog? = null
-
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -84,7 +80,7 @@ class QuickSearchFragment : Fragment() {
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
)
searchViewModel = ViewModelProvider(this)[SearchViewModel::class.java]
- bottomSheetDialog?.ownShow()
+
return inflater.inflate(R.layout.quick_search, container, false)
}
@@ -160,9 +156,7 @@ class QuickSearchFragment : Fragment() {
// else -> SearchHelper.handleSearchClickCallback(activity, callback)
//}
}, { item ->
- bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = {
- bottomSheetDialog = null
- })
+ activity?.loadHomepageList(item)
})
quick_search_master_recycler?.layoutManager = GridLayoutManager(context, 1)
}
@@ -220,7 +214,7 @@ class QuickSearchFragment : Fragment() {
when (it) {
is Resource.Success -> {
it.value.let { data ->
- (quick_search_autofit_results?.adapter as? SearchAdapter)?.updateList(
+ (quick_search_autofit_results?.adapter as? SearchAdapter?)?.updateList(
context?.filterSearchResultByFilmQuality(data) ?: data
)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt
index 0932b001..e9fbd5f9 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt
@@ -58,7 +58,6 @@ const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14
const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16
const val ACTION_PLAY_EPISODE_IN_MPV = 17
-const val ACTION_MARK_AS_WATCHED = 18
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
@@ -218,18 +217,10 @@ class EpisodeAdapter(
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
episodeText.isSelected = true // is needed for text repeating
- if (card.videoWatchState == VideoWatchState.Watched) {
- // This cannot be done in getDisplayPosition() as when you have not watched something
- // the duration and position is 0
- episodeProgress?.max = 1
- episodeProgress?.progress = 1
- episodeProgress?.isVisible = true
- } else {
- val displayPos = card.getDisplayPosition()
- episodeProgress?.max = (card.duration / 1000).toInt()
- episodeProgress?.progress = (displayPos / 1000).toInt()
- episodeProgress?.isVisible = displayPos > 0L
- }
+ val displayPos = card.getDisplayPosition()
+ episodeProgress?.max = (card.duration / 1000).toInt()
+ episodeProgress?.progress = (displayPos / 1000).toInt()
+ episodeProgress?.isVisible = displayPos > 0L
episodePoster?.isVisible = episodePoster?.setImage(card.poster) == true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt
index affbcbb4..59a46264 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt
@@ -7,13 +7,13 @@ import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.mvvm.logError
fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) {
- if (this == null) return
+ if(this == null) return
this.layoutManager =
this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } }
?: this.layoutManager
}
-open class LinearListLayout(context: Context?) :
+class LinearListLayout(context: Context?) :
LinearLayoutManager(context) {
fun setHorizontal() {
@@ -24,8 +24,7 @@ open class LinearListLayout(context: Context?) :
orientation = VERTICAL
}
- private fun getCorrectParent(focused: View?): View? {
- if (focused == null) return null
+ private fun getCorrectParent(focused: View): View? {
var current: View? = focused
val last: ArrayList = arrayListOf(focused)
while (current != null && current !is RecyclerView) {
@@ -55,17 +54,10 @@ open class LinearListLayout(context: Context?) :
linearSmoothScroller.targetPosition = position
startSmoothScroll(linearSmoothScroller)
}*/
+
override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
val dir = if (orientation == HORIZONTAL) {
- if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) {
- // This scrolls the recyclerview before doing focus search, which
- // allows the focus search to work better.
-
- // Without this the recyclerview focus location on the screen
- // would change when scrolling between recyclerviews.
- (focused.parent as? RecyclerView)?.focusSearch(direction)
- return null
- }
+ if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) return null
if (direction == View.FOCUS_RIGHT) 1 else -1
} else {
if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
index 5a3e28b4..26dbb03e 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
@@ -15,28 +15,24 @@ import android.view.ViewGroup
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.ImageView
-import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
-import androidx.recyclerview.widget.RecyclerView
import com.discord.panels.OverlappingPanelsLayout
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
-import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.mvvm.*
-import com.lagradost.cloudstream3.services.SubscriptionWorkManager
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
@@ -53,7 +49,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
-import com.lagradost.cloudstream3.utils.DataStoreHelper.getVideoWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
@@ -88,8 +83,6 @@ import kotlinx.android.synthetic.main.fragment_result.result_next_airing
import kotlinx.android.synthetic.main.fragment_result.result_next_airing_time
import kotlinx.android.synthetic.main.fragment_result.result_no_episodes
import kotlinx.android.synthetic.main.fragment_result.result_play_movie
-import kotlinx.android.synthetic.main.fragment_result.result_poster
-import kotlinx.android.synthetic.main.fragment_result.result_poster_holder
import kotlinx.android.synthetic.main.fragment_result.result_reload_connection_open_in_browser
import kotlinx.android.synthetic.main.fragment_result.result_reload_connectionerror
import kotlinx.android.synthetic.main.fragment_result.result_resume_parent
@@ -111,15 +104,6 @@ import kotlinx.coroutines.runBlocking
const val START_ACTION_RESUME_LATEST = 1
const val START_ACTION_LOAD_EP = 2
-/**
- * Future proofed way to mark episodes as watched
- **/
-enum class VideoWatchState {
- /** Default value when no key is set */
- None,
- Watched
-}
-
data class ResultEpisode(
val headerName: String,
val name: String?,
@@ -138,10 +122,6 @@ data class ResultEpisode(
val isFiller: Boolean?,
val tvType: TvType,
val parentId: Int,
- /**
- * Conveys if the episode itself is marked as watched
- **/
- val videoWatchState: VideoWatchState
)
fun ResultEpisode.getRealPosition(): Long {
@@ -178,7 +158,6 @@ fun buildResultEpisode(
parentId: Int,
): ResultEpisode {
val posDur = getViewPos(id)
- val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None
return ResultEpisode(
headerName,
name,
@@ -197,7 +176,6 @@ fun buildResultEpisode(
isFiller,
tvType,
parentId,
- videoWatchState
)
}
@@ -281,7 +259,7 @@ open class ResultFragment : ResultTrailerPlayer() {
private var downloadButton: EasyDownloadButton? = null
override fun onDestroyView() {
updateUIListener = null
- (result_episodes?.adapter as? EpisodeAdapter)?.killAdapter()
+ (result_episodes?.adapter as EpisodeAdapter?)?.killAdapter()
downloadButton?.dispose()
super.onDestroyView()
@@ -462,7 +440,7 @@ open class ResultFragment : ResultTrailerPlayer() {
temporary_no_focus?.requestFocus()
}
- (result_episodes?.adapter as? EpisodeAdapter)?.updateList(episodes.value)
+ (result_episodes?.adapter as? EpisodeAdapter?)?.updateList(episodes.value)
if (isTv && hasEpisodes) main {
delay(500)
@@ -513,10 +491,11 @@ open class ResultFragment : ResultTrailerPlayer() {
return StoredData(url, apiName, showFillers, dubStatus, start, playerAction)
}
- private fun reloadViewModel(forceReload: Boolean) {
- if (!viewModel.hasLoaded() || forceReload) {
+ private fun reloadViewModel(success: Boolean = false) {
+ if (!viewModel.hasLoaded()) {
val storedData = getStoredData(activity ?: context ?: return) ?: return
+ //viewModel.clear()
viewModel.load(
activity,
storedData.url ?: return,
@@ -532,25 +511,6 @@ open class ResultFragment : ResultTrailerPlayer() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- result_cast_items?.layoutManager = object : LinearListLayout(view.context) {
- override fun onRequestChildFocus(
- parent: RecyclerView,
- state: RecyclerView.State,
- child: View,
- focused: View?
- ): Boolean {
- // Make the cast always focus the first visible item when focused
- // from somewhere else. Otherwise it jumps to the last item.
- return if (parent.focusedChild == null) {
- scrollToPosition(this.findFirstCompletelyVisibleItemPosition())
- true
- } else {
- super.onRequestChildFocus(parent, state, child, focused)
- }
- }
- }.apply {
- this.orientation = RecyclerView.HORIZONTAL
- }
result_cast_items?.adapter = ActorAdaptor()
updateUIListener = ::updateUI
@@ -598,19 +558,6 @@ open class ResultFragment : ResultTrailerPlayer() {
)
- observe(viewModel.episodeSynopsis) { description ->
- view.context?.let { ctx ->
- val builder: AlertDialog.Builder =
- AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
- builder.setMessage(description.html())
- .setTitle(R.string.synopsis)
- .setOnDismissListener {
- viewModel.releaseEpisodeSynopsis()
- }
- .show()
- }
- }
-
observe(viewModel.watchStatus) { watchType ->
result_bookmark_button?.text = getString(watchType.stringRes)
result_bookmark_fab?.text = getString(watchType.stringRes)
@@ -710,7 +657,7 @@ open class ResultFragment : ResultTrailerPlayer() {
val newList = list.filter { it.isSynced && it.hasAccount }
result_mini_sync?.isVisible = newList.isNotEmpty()
- (result_mini_sync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon })
+ (result_mini_sync?.adapter as? ImageAdapter?)?.updateList(newList.mapNotNull { it.icon })
}
var currentSyncProgress = 0
@@ -873,7 +820,6 @@ open class ResultFragment : ResultTrailerPlayer() {
}
observe(viewModel.page) { data ->
- if (data == null) return@observe
when (data) {
is Resource.Success -> {
val d = data.value
@@ -923,40 +869,10 @@ open class ResultFragment : ResultTrailerPlayer() {
result_cast_items?.isVisible = d.actors != null
- (result_cast_items?.adapter as? ActorAdaptor)?.apply {
+ (result_cast_items?.adapter as ActorAdaptor?)?.apply {
updateList(d.actors ?: emptyList())
}
- observeNullable(viewModel.subscribeStatus) { isSubscribed ->
- result_subscribe?.isVisible = isSubscribed != null
- if (isSubscribed == null) return@observeNullable
-
- val drawable = if (isSubscribed) {
- R.drawable.ic_baseline_notifications_active_24
- } else {
- R.drawable.baseline_notifications_none_24
- }
-
- result_subscribe?.setImageResource(drawable)
- }
-
- result_subscribe?.setOnClickListener {
- val isSubscribed =
- viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener
-
- val message = if (isSubscribed) {
- // Kinda icky to have this here, but it works.
- SubscriptionWorkManager.enqueuePeriodicWork(context)
- R.string.subscription_new
- } else {
- R.string.subscription_deleted
- }
-
- val name = (viewModel.page.value as? Resource.Success)?.value?.title
- ?: txt(R.string.no_data).asStringNull(context) ?: ""
- showToast(activity, txt(message, name), Toast.LENGTH_SHORT)
- }
-
result_open_in_browser?.isVisible = d.url.startsWith("http")
result_open_in_browser?.setOnClickListener {
val i = Intent(ACTION_VIEW)
@@ -1004,6 +920,8 @@ open class ResultFragment : ResultTrailerPlayer() {
}
+ result_tag?.removeAllViews()
+
d.comingSoon.let { soon ->
result_coming_soon?.isVisible = soon
result_data_holder?.isGone = soon
@@ -1012,7 +930,6 @@ open class ResultFragment : ResultTrailerPlayer() {
val tags = d.tags
result_tag_holder?.isVisible = tags.isNotEmpty()
result_tag?.apply {
- removeAllViews()
tags.forEach { tag ->
val chip = Chip(context)
val chipDrawable = ChipDrawable.createFromAttributes(
@@ -1027,7 +944,6 @@ open class ResultFragment : ResultTrailerPlayer() {
chip.isCheckable = false
chip.isFocusable = false
chip.isClickable = false
- chip.setTextColor(context.colorFromAttribute(R.attr.textColor))
addView(chip)
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt
index 2f232995..9bae8753 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt
@@ -322,7 +322,9 @@ class ResultFragmentPhone : ResultFragment() {
// it?.dismiss()
//}
builder.setCanceledOnTouchOutside(true)
+
builder.show()
+
builder
}
}
@@ -483,7 +485,7 @@ class ResultFragmentPhone : ResultFragment() {
result_recommendations?.post {
rec?.let { list ->
- (result_recommendations?.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst })
+ (result_recommendations?.adapter as SearchAdapter?)?.updateList(list.filter { it.apiName == matchAgainst })
}
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
index 71ecb0e9..0e3ee53e 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
@@ -3,28 +3,30 @@ package com.lagradost.cloudstream3.ui.result
import android.app.Dialog
import android.os.Bundle
import android.view.View
+import android.widget.LinearLayout
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialog
-import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.DubStatus
-import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.mvvm.ResourceSome
import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.observe
-import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator
-import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
-import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.AppUtils.setMaxViewPoolSize
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
-import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
+import kotlinx.android.synthetic.main.fragment_home.*
+import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.android.synthetic.main.fragment_result_tv.*
+import kotlinx.android.synthetic.main.fragment_result_tv.result_episodes
+import kotlinx.android.synthetic.main.fragment_result_tv.result_episodes_text
+import kotlinx.android.synthetic.main.fragment_result_tv.result_play_movie
+import kotlinx.android.synthetic.main.fragment_result_tv.result_root
class ResultFragmentTv : ResultFragment() {
override val resultLayout = R.layout.fragment_result_tv
@@ -83,31 +85,13 @@ class ResultFragmentTv : ResultFragment() {
}
}
- override fun setTrailers(trailers: List?) {
- context?.updateHasTrailers()
- if (!LoadResponse.isTrailersEnabled) return
-
- result_play_trailer?.isGone = trailers.isNullOrEmpty()
- result_play_trailer?.setOnClickListener {
- if (trailers.isNullOrEmpty()) return@setOnClickListener
- activity.navigate(
- R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
- ExtractorLinkGenerator(
- trailers,
- emptyList()
- )
- )
- )
- }
- }
-
override fun setRecommendations(rec: List?, validApiName: String?) {
currentRecommendations = rec ?: emptyList()
val isInvalid = rec.isNullOrEmpty()
result_recommendations?.isGone = isInvalid
result_recommendations_holder?.isGone = isInvalid
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
- (result_recommendations?.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst }
+ (result_recommendations?.adapter as SearchAdapter?)?.updateList(rec?.filter { it.apiName == matchAgainst }
?: emptyList())
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
@@ -126,7 +110,7 @@ class ResultFragmentTv : ResultFragment() {
super.onViewCreated(view, savedInstanceState)
result_episodes?.layoutManager =
- //LinearListLayout(result_episodes ?: return, result_episodes?.context).apply {
+ //LinearListLayout(result_episodes ?: return, result_episodes?.context).apply {
LinearListLayout(result_episodes?.context).apply {
setHorizontal()
}
@@ -176,7 +160,8 @@ class ResultFragmentTv : ResultFragment() {
loadingDialog = null
}
loadingDialog = loadingDialog ?: context?.let { ctx ->
- val builder = BottomSheetDialog(ctx)
+ val builder =
+ BottomSheetDialog(ctx)
builder.setContentView(R.layout.bottom_loading)
builder.setOnDismissListener {
loadingDialog = null
@@ -186,7 +171,9 @@ class ResultFragmentTv : ResultFragment() {
// it?.dismiss()
//}
builder.setCanceledOnTouchOutside(true)
+
builder.show()
+
builder
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
index 46a8c9f6..0c26f69c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
@@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.result
import android.app.Activity
import android.content.*
import android.net.Uri
-import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
@@ -14,10 +13,8 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.*
-import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.APIHolder.unixTime
-import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.getCastSession
import com.lagradost.cloudstream3.CommonActivity.showToast
@@ -27,7 +24,6 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId
import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
import com.lagradost.cloudstream3.mvvm.*
-import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.APIRepository
@@ -59,6 +55,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import kotlinx.coroutines.*
import java.io.File
+import java.lang.Math.abs
import java.util.concurrent.TimeUnit
@@ -317,11 +314,6 @@ data class ExtractedTrailerData(
class ResultViewModel2 : ViewModel() {
private var currentResponse: LoadResponse? = null
- fun clear() {
- currentResponse = null
- _page.postValue(null)
- }
-
data class EpisodeIndexer(
val dubStatus: DubStatus,
val season: Int,
@@ -348,9 +340,9 @@ class ResultViewModel2 : ViewModel() {
//private val currentHeaderName get() = currentResponse?.name
- private val _page: MutableLiveData?> =
- MutableLiveData(null)
- val page: LiveData?> = _page
+ private val _page: MutableLiveData> =
+ MutableLiveData(Resource.Loading())
+ val page: LiveData> = _page
private val _episodes: MutableLiveData>> =
MutableLiveData(ResourceSome.Loading())
@@ -406,6 +398,7 @@ class ResultViewModel2 : ViewModel() {
private val _selectedDubStatusIndex: MutableLiveData = MutableLiveData(-1)
val selectedDubStatusIndex: LiveData = _selectedDubStatusIndex
+
private val _loadedLinks: MutableLiveData> = MutableLiveData(Some.None)
val loadedLinks: LiveData> = _loadedLinks
@@ -413,12 +406,6 @@ class ResultViewModel2 : ViewModel() {
MutableLiveData(Some.None)
val resumeWatching: LiveData> = _resumeWatching
- private val _episodeSynopsis: MutableLiveData = MutableLiveData(null)
- val episodeSynopsis: LiveData = _episodeSynopsis
-
- private val _subscribeStatus: MutableLiveData = MutableLiveData(null)
- val subscribeStatus: LiveData = _subscribeStatus
-
companion object {
const val TAG = "RVM2"
private const val EPISODE_RANGE_SIZE = 20
@@ -431,6 +418,7 @@ class ResultViewModel2 : ViewModel() {
fun updateWatchStatus(currentResponse: LoadResponse, status: WatchType) {
val currentId = currentResponse.getId()
+ val resultPage = currentResponse
DataStoreHelper.setResultWatchState(currentId, status.internalId)
val current = DataStoreHelper.getBookmarkedData(currentId)
@@ -441,12 +429,12 @@ class ResultViewModel2 : ViewModel() {
currentId,
current?.bookmarkedTime ?: currentTime,
currentTime,
- currentResponse.name,
- currentResponse.url,
- currentResponse.apiName,
- currentResponse.type,
- currentResponse.posterUrl,
- currentResponse.year
+ resultPage.name,
+ resultPage.url,
+ resultPage.apiName,
+ resultPage.type,
+ resultPage.posterUrl,
+ resultPage.year
)
)
}
@@ -820,42 +808,6 @@ class ResultViewModel2 : ViewModel() {
}
}
- /**
- * @return true if the new status is Subscribed, false if not. Null if not possible to subscribe.
- **/
- fun toggleSubscriptionStatus(): Boolean? {
- val isSubscribed = _subscribeStatus.value ?: return null
- val response = currentResponse ?: return null
- if (response !is EpisodeResponse) return null
-
- val currentId = response.getId()
-
- if (isSubscribed) {
- DataStoreHelper.removeSubscribedData(currentId)
- } else {
- val current = DataStoreHelper.getSubscribedData(currentId)
-
- DataStoreHelper.setSubscribedData(
- currentId,
- DataStoreHelper.SubscribedData(
- currentId,
- current?.bookmarkedTime ?: unixTimeMS,
- unixTimeMS,
- response.getLatestEpisodes(),
- response.name,
- response.url,
- response.apiName,
- response.type,
- response.posterUrl,
- response.year
- )
- )
- }
-
- _subscribeStatus.postValue(!isSubscribed)
- return !isSubscribed
- }
-
private fun startChromecast(
activity: Activity?,
result: ResultEpisode,
@@ -1126,12 +1078,7 @@ class ResultViewModel2 : ViewModel() {
1L
}
- // Component no longer safe to use in A13 for VLC
- // https://code.videolan.org/videolan/vlc-android/-/issues/2776
- // This will likely need to be updated once VLC fixes their documentation.
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
- component = VLC_COMPONENT
- }
+ component = VLC_COMPONENT
putExtra("from_start", !resume)
putExtra("position", position)
@@ -1166,10 +1113,6 @@ class ResultViewModel2 : ViewModel() {
)
)
- fun releaseEpisodeSynopsis() {
- _episodeSynopsis.postValue(null)
- }
-
private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) {
when (click.action) {
ACTION_SHOW_OPTIONS -> {
@@ -1206,17 +1149,6 @@ class ResultViewModel2 : ViewModel() {
)
)
- // Do not add mark as watched on movies
- if (!listOf(TvType.Movie, TvType.AnimeMovie).contains(click.data.tvType)) {
- val isWatched =
- DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched
-
- val watchedText = if (isWatched) R.string.action_remove_from_watched
- else R.string.action_mark_as_watched
-
- options.add(txt(watchedText) to ACTION_MARK_AS_WATCHED)
- }
-
postPopup(
txt(
activity?.getNameFull(
@@ -1249,10 +1181,6 @@ class ResultViewModel2 : ViewModel() {
}
}
}
- ACTION_SHOW_DESCRIPTION -> {
- _episodeSynopsis.postValue(click.data.description)
- }
-
/* not implemented, not used
ACTION_DOWNLOAD_EPISODE_SUBTITLE -> {
loadLinks(click.data, isVisible = false, isCasting = false) { links ->
@@ -1437,7 +1365,7 @@ class ResultViewModel2 : ViewModel() {
R.id.global_to_navigation_player,
GeneratorPlayer.newInstance(
generator?.also {
- it.getAll() // I know kinda shit to iterate all, but it is 100% sure to work
+ it.getAll() // I know kinda shit to itterate all, but it is 100% sure to work
?.indexOfFirst { value -> value is ResultEpisode && value.id == click.data.id }
?.let { index ->
if (index >= 0)
@@ -1448,19 +1376,6 @@ class ResultViewModel2 : ViewModel() {
)
)
}
- ACTION_MARK_AS_WATCHED -> {
- val isWatched =
- DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched
-
- if (isWatched) {
- DataStoreHelper.setVideoWatchState(click.data.id, VideoWatchState.None)
- } else {
- DataStoreHelper.setVideoWatchState(click.data.id, VideoWatchState.Watched)
- }
-
- // Kinda dirty to reload all episodes :(
- reloadEpisodes()
- }
}
}
@@ -1469,128 +1384,79 @@ class ResultViewModel2 : ViewModel() {
meta: SyncAPI.SyncResult?,
syncs: Map? = null
): Pair {
- //if (meta == null) return resp to false
+ if (meta == null) return resp to false
var updateEpisodes = false
val out = resp.apply {
Log.i(TAG, "applyMeta")
- if (meta != null) {
- duration = duration ?: meta.duration
- rating = rating ?: meta.publicScore
- tags = tags ?: meta.genres
- plot = if (plot.isNullOrBlank()) meta.synopsis else plot
- posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
- actors = actors ?: meta.actors
+ duration = duration ?: meta.duration
+ rating = rating ?: meta.publicScore
+ tags = tags ?: meta.genres
+ plot = if (plot.isNullOrBlank()) meta.synopsis else plot
+ posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl
+ actors = actors ?: meta.actors
- if (this is EpisodeResponse) {
- nextAiring = nextAiring ?: meta.nextAiring
- }
-
- val realRecommendations = ArrayList()
- val apiNames = apis.filter {
- it.name.contains("gogoanime", true) ||
- it.name.contains("9anime", true)
- }.map {
- it.name
- }
-
- meta.recommendations?.forEach { rec ->
- apiNames.forEach { name ->
- realRecommendations.add(rec.copy(apiName = name))
- }
- }
-
- recommendations = recommendations?.union(realRecommendations)?.toList()
- ?: realRecommendations
+ if (this is EpisodeResponse) {
+ nextAiring = nextAiring ?: meta.nextAiring
}
for ((k, v) in syncs ?: emptyMap()) {
syncData[k] = v
}
- argamap(
- {
- if (this !is AnimeLoadResponse) return@argamap
- // already exist, no need to run getTracker
- if (this.getAniListId() != null && this.getMalId() != null) return@argamap
+ val realRecommendations = ArrayList()
+ // TODO: fix
+ //val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name)
+ // meta.recommendations?.forEach { rec ->
+ // apiNames.forEach { name ->
+ // realRecommendations.add(rec.copy(apiName = name))
+ // }
+ // }
- val res = APIHolder.getTracker(
- listOfNotNull(
- this.engName,
- this.name,
- this.japName
- ).filter { it.length > 2 }
- .distinct(), // the reason why we filter is due to not wanting smth like " " or "?"
- TrackerType.getTypes(this.type),
- this.year
+ recommendations = recommendations?.union(realRecommendations)?.toList()
+ ?: realRecommendations
+
+ argamap({
+ addTrailer(meta.trailers)
+ }, {
+ if (this !is AnimeLoadResponse) return@argamap
+ val map =
+ Kitsu.getEpisodesDetails(
+ getMalId(),
+ getAniListId(),
+ isResponseRequired = false
)
-
- val ids = arrayOf(
- AccountManager.malApi.idPrefix to res?.malId?.toString(),
- AccountManager.aniListApi.idPrefix to res?.aniId
- )
-
- if (ids.any { (id, new) ->
- val current = syncData[id]
- new != null && current != null && current != new
- }
- ) {
- // getTracker fucked up as it conflicts with current implementation
- return@argamap
- }
-
- // set all the new data, prioritise old correct data
- ids.forEach { (id, new) ->
- new?.let {
- syncData[id] = syncData[id] ?: it
- }
- }
-
- // set posters, might fuck up due to headers idk
- posterUrl = posterUrl ?: res?.image
- backgroundPosterUrl = backgroundPosterUrl ?: res?.cover
- },
- {
- if (meta == null) return@argamap
- addTrailer(meta.trailers)
- }, {
- if (this !is AnimeLoadResponse) return@argamap
- val map =
- Kitsu.getEpisodesDetails(
- getMalId(),
- getAniListId(),
- isResponseRequired = false
- )
- if (map.isNullOrEmpty()) return@argamap
- updateEpisodes = DubStatus.values().map { dubStatus ->
- val current =
- this.episodes[dubStatus]?.mapIndexed { index, episode ->
- episode.apply {
- this.episode = this.episode ?: (index + 1)
- }
- }?.sortedBy { it.episode ?: 0 }?.toMutableList()
- if (current.isNullOrEmpty()) return@map false
- val episodeNumbers = current.map { ep -> ep.episode!! }
- var updateCount = 0
- map.forEach { (episode, node) ->
- episodeNumbers.binarySearch(episode).let { index ->
- current.getOrNull(index)?.let { currentEp ->
- current[index] = currentEp.apply {
- updateCount++
- this.description = this.description ?: node.description?.en
- this.name = this.name ?: node.titles?.canonical
- this.episode =
- this.episode ?: node.num ?: episodeNumbers[index]
- this.posterUrl =
- this.posterUrl ?: node.thumbnail?.original?.url
- }
+ if (map.isNullOrEmpty()) return@argamap
+ updateEpisodes = DubStatus.values().map { dubStatus ->
+ val current =
+ this.episodes[dubStatus]?.mapIndexed { index, episode ->
+ episode.apply {
+ this.episode = this.episode ?: (index + 1)
+ }
+ }?.sortedBy { it.episode ?: 0 }?.toMutableList()
+ if (current.isNullOrEmpty()) return@map false
+ val episodeNumbers = current.map { ep -> ep.episode!! }
+ var updateCount = 0
+ map.forEach { (episode, node) ->
+ episodeNumbers.binarySearch(episode).let { index ->
+ current.getOrNull(index)?.let { currentEp ->
+ current[index] = currentEp.apply {
+ updateCount++
+ val currentBack = this
+ this.description = this.description ?: node.description?.en
+ this.name = this.name ?: node.titles?.canonical
+ this.episode =
+ this.episode ?: node.num ?: episodeNumbers[index]
+ this.posterUrl =
+ this.posterUrl ?: node.thumbnail?.original?.url
}
}
}
- this.episodes[dubStatus] = current
- updateCount > 0
- }.any { it }
- })
+ }
+ this.episodes[dubStatus] = current
+ updateCount > 0
+ }.any { it }
+ })
}
return out to updateEpisodes
}
@@ -1658,13 +1524,7 @@ class ResultViewModel2 : ViewModel() {
val end = minOf(list.size, start + length)
list.subList(start, end).map {
val posDur = getViewPos(it.id)
- val watchState =
- DataStoreHelper.getVideoWatchState(it.id) ?: VideoWatchState.None
- it.copy(
- position = posDur?.position ?: 0,
- duration = posDur?.duration ?: 0,
- videoWatchState = watchState
- )
+ it.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0)
}
}
?: emptyList()
@@ -1717,33 +1577,24 @@ class ResultViewModel2 : ViewModel() {
postResume()
}
- private fun postSubscription(loadResponse: LoadResponse) {
- if (loadResponse.isEpisodeBased()) {
- val id = loadResponse.getId()
- val data = DataStoreHelper.getSubscribedData(id)
- DataStoreHelper.updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
- val isSubscribed = data != null
- _subscribeStatus.postValue(isSubscribed)
- }
- }
-
private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) {
if (range == null || indexer == null) {
return
}
+ val episodes = currentEpisodes[indexer]
val ranges = currentRanges[indexer]
if (ranges?.contains(range) != true) {
// if the current ranges does not include the range then select the range with the closest matching start episode
// this usually happends when dub has less episodes then sub -> the range does not exist
- ranges?.minByOrNull { kotlin.math.abs(it.startEpisode - range.startEpisode) }
- ?.let { r ->
- postEpisodeRange(indexer, r)
- return
- }
+ ranges?.minByOrNull { abs(it.startEpisode - range.startEpisode) }?.let { r ->
+ postEpisodeRange(indexer, r)
+ return
+ }
}
+ val size = episodes?.size
val isMovie = currentResponse?.isMovie() == true
currentIndex = indexer
currentRange = range
@@ -1753,7 +1604,6 @@ class ResultViewModel2 : ViewModel() {
text to r
} ?: emptyList())
- val size = currentEpisodes[indexer]?.size
_episodesCountText.postValue(
some(
if (isMovie) null else
@@ -1833,12 +1683,9 @@ class ResultViewModel2 : ViewModel() {
generator = if (isMovie) {
getMovie()?.let { RepoLinkGenerator(listOf(it), page = currentResponse) }
} else {
- val episodes = currentEpisodes.filter { it.key.dubStatus == indexer.dubStatus }
- .toList()
- .sortedBy { it.first.season }
- .flatMap { it.second }
-
- RepoLinkGenerator(episodes, page = currentResponse)
+ episodes?.let { list ->
+ RepoLinkGenerator(list, page = currentResponse)
+ }
}
if (isMovie) {
@@ -1863,7 +1710,6 @@ class ResultViewModel2 : ViewModel() {
) {
currentResponse = loadResponse
postPage(loadResponse, apiRepository)
- postSubscription(loadResponse)
if (updateEpisodes)
postEpisodes(loadResponse, updateFillers)
}
@@ -2073,7 +1919,7 @@ class ResultViewModel2 : ViewModel() {
// this takes the indexer most preferable by the user given the current sorting
val min = ranges.keys.minByOrNull { index ->
kotlin.math.abs(
- index.season - (preferStartSeason ?: 1)
+ index.season - (preferStartSeason ?: 0)
) + if (index.dubStatus == preferDubStatus) 0 else 100000
}
@@ -2129,42 +1975,42 @@ class ResultViewModel2 : ViewModel() {
limit: Int = 0
): List =
coroutineScope {
- val returnlist = ArrayList()
- loadResponse.trailers.windowed(limit, limit, true).takeWhile { list ->
- list.amap { trailerData ->
- try {
- val links = arrayListOf()
- val subs = arrayListOf()
- if (!loadExtractor(
+ var currentCount = 0
+ return@coroutineScope loadResponse.trailers.amap { trailerData ->
+ try {
+ val links = arrayListOf()
+ val subs = arrayListOf()
+ if (!loadExtractor(
+ trailerData.extractorUrl,
+ trailerData.referer,
+ { subs.add(it) },
+ { links.add(it) }) && trailerData.raw
+ ) {
+ arrayListOf(
+ ExtractorLink(
+ "",
+ "Trailer",
trailerData.extractorUrl,
- trailerData.referer,
- { subs.add(it) },
- { links.add(it) }) && trailerData.raw
- ) {
- arrayListOf(
- ExtractorLink(
- "",
- "Trailer",
- trailerData.extractorUrl,
- trailerData.referer ?: "",
- Qualities.Unknown.value,
- trailerData.extractorUrl.contains(".m3u8")
- )
- ) to arrayListOf()
- } else {
- links to subs
+ trailerData.referer ?: "",
+ Qualities.Unknown.value,
+ trailerData.extractorUrl.contains(".m3u8")
+ )
+ ) to arrayListOf()
+ } else {
+ links to subs
+ }.also { (extractor, _) ->
+ if (extractor.isNotEmpty() && limit != 0) {
+ currentCount++
+ if (currentCount >= limit) {
+ cancel()
+ }
}
- } catch (e: Throwable) {
- logError(e)
- null
}
- }.filterNotNull().map { (links, subs) -> ExtractedTrailerData(links, subs) }.let {
- returnlist.addAll(it)
+ } catch (e: Throwable) {
+ logError(e)
+ null
}
-
- returnlist.size < limit
- }
- return@coroutineScope returnlist
+ }.filterNotNull().map { (links, subs) -> ExtractedTrailerData(links, subs) }
}
@@ -2223,9 +2069,8 @@ class ResultViewModel2 : ViewModel() {
showFillers: Boolean,
dubStatus: DubStatus,
autostart: AutoResume?,
- loadTrailers: Boolean = true,
) =
- ioSafe {
+ viewModelScope.launchSafe {
_page.postValue(Resource.Loading(url))
_episodes.postValue(ResourceSome.Loading())
@@ -2243,7 +2088,7 @@ class ResultViewModel2 : ViewModel() {
"This provider does not exist"
)
)
- return@ioSafe
+ return@launchSafe
}
@@ -2251,18 +2096,24 @@ class ResultViewModel2 : ViewModel() {
val validUrlResource = safeApiCall {
SyncRedirector.redirect(
url,
- api
+ api.mainUrl
)
}
-
+ // TODO: fix
+ // val validUrlResource = safeApiCall {
+ // SyncRedirector.redirect(
+ // url,
+ // api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime")
+ // .replace(GogoanimeProvider().mainUrl, "gogoanime")
+ // )
+ // }
if (validUrlResource !is Resource.Success) {
if (validUrlResource is Resource.Failure) {
_page.postValue(validUrlResource)
}
- return@ioSafe
+ return@launchSafe
}
-
val validUrl = validUrlResource.value
val repo = APIRepository(api)
currentRepo = repo
@@ -2272,16 +2123,16 @@ class ResultViewModel2 : ViewModel() {
_page.postValue(data)
}
is Resource.Success -> {
- if (!isActive) return@ioSafe
+ if (!isActive) return@launchSafe
val loadResponse = ioWork {
applyMeta(data.value, currentMeta, currentSync).first
}
- if (!isActive) return@ioSafe
+ if (!isActive) return@launchSafe
val mainId = loadResponse.getId()
preferDubStatus = getDub(mainId) ?: preferDubStatus
preferStartEpisode = getResultEpisode(mainId)
- preferStartSeason = getResultSeason(mainId) ?: 1
+ preferStartSeason = getResultSeason(mainId)
setKey(
DOWNLOAD_HEADER_CACHE,
@@ -2296,15 +2147,15 @@ class ResultViewModel2 : ViewModel() {
System.currentTimeMillis(),
)
)
- if (loadTrailers)
- loadTrailers(data.value)
+
+ loadTrailers(data.value)
postSuccessful(
data.value,
updateEpisodes = true,
updateFillers = showFillers,
apiRepository = repo
)
- if (!isActive) return@ioSafe
+ if (!isActive) return@launchSafe
handleAutoStart(activity, autostart)
}
is Resource.Loading -> {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt
index 649641c8..c2523931 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt
@@ -10,37 +10,27 @@ import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
-import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import kotlinx.android.synthetic.main.search_result_compact.view.*
import kotlin.math.roundToInt
-/** Click */
const val SEARCH_ACTION_LOAD = 0
-
-/** Long press */
const val SEARCH_ACTION_SHOW_METADATA = 1
const val SEARCH_ACTION_PLAY_FILE = 2
const val SEARCH_ACTION_FOCUSED = 4
-class SearchClickCallback(
- val action: Int,
- val view: View,
- val position: Int,
- val card: SearchResponse
-)
+class SearchClickCallback(val action: Int, val view: View, val position : Int, val card: SearchResponse)
class SearchAdapter(
private val cardList: MutableList,
private val resView: AutofitRecyclerView,
private val clickCallback: (SearchClickCallback) -> Unit,
) : RecyclerView.Adapter() {
- var hasNext: Boolean = false
+ var hasNext : Boolean = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- val layout =
- if (parent.context.IsBottomLayout()) R.layout.search_result_grid_expanded else R.layout.search_result_grid
+ val layout = if(parent.context.IsBottomLayout()) R.layout.search_result_grid_expanded else R.layout.search_result_grid
return CardViewHolder(
LayoutInflater.from(parent.context).inflate(layout, parent, false),
clickCallback,
@@ -81,8 +71,7 @@ class SearchAdapter(
val cardView: ImageView = itemView.imageView
private val compactView = false//itemView.context.getGridIsCompact()
- private val coverHeight: Int =
- if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt()
+ private val coverHeight: Int = if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt()
fun bind(card: SearchResponse, position: Int) {
if (!compactView) {
@@ -99,10 +88,7 @@ class SearchAdapter(
}
}
-class SearchResponseDiffCallback(
- private val oldList: List