diff --git a/app/src/main/java/com/fongmi/android/tv/player/Players.java b/app/src/main/java/com/fongmi/android/tv/player/Players.java index 8b346b4f9..94313bb82 100644 --- a/app/src/main/java/com/fongmi/android/tv/player/Players.java +++ b/app/src/main/java/com/fongmi/android/tv/player/Players.java @@ -1,7 +1,10 @@ package com.fongmi.android.tv.player; import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.text.TextUtils; @@ -24,9 +27,11 @@ import com.fongmi.android.tv.bean.Track; import com.fongmi.android.tv.event.ErrorEvent; import com.fongmi.android.tv.event.PlayerEvent; import com.fongmi.android.tv.impl.ParseCallback; +import com.fongmi.android.tv.impl.SessionCallback; import com.fongmi.android.tv.utils.Notify; import com.fongmi.android.tv.utils.ResUtil; -import com.github.catvod.crawler.SpiderDebug; +import com.fongmi.android.tv.utils.Utils; +import com.orhanobut.logger.Logger; import java.util.Formatter; import java.util.List; @@ -40,6 +45,8 @@ import tv.danmaku.ijk.media.player.ui.IjkVideoView; public class Players implements Player.Listener, IMediaPlayer.Listener, AnalyticsListener, ParseCallback, DrawHandler.Callback { + private static final String TAG = Players.class.getSimpleName(); + public static final int SYS = 0; public static final int IJK = 1; public static final int EXO = 2; @@ -89,6 +96,11 @@ public class Players implements Player.Listener, IMediaPlayer.Listener, Analytic private void createSession(Activity activity) { session = new MediaSessionCompat(activity, "TV"); + session.setMediaButtonReceiver(null); + session.setCallback(SessionCallback.create(this)); + session.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); + session.setSessionActivity(PendingIntent.getActivity(App.get(), 99, new Intent(App.get(), activity.getClass()), Utils.getPendingFlag())); + MediaControllerCompat.setMediaController(activity, session.getController()); } public void set(PlayerView exo, IjkVideoView ijk) { @@ -409,26 +421,26 @@ public class Players implements Player.Listener, IMediaPlayer.Listener, Analytic } private void setMediaSource(Result result) { - SpiderDebug.log(errorCode + "," + result.getRealUrl()); - if (isIjk()) ijkPlayer.setMediaSource(IjkUtil.getSource(result)); - if (isExo()) exoPlayer.setMediaSource(ExoUtil.getSource(result, errorCode)); - if (isExo()) exoPlayer.prepare(); + Logger.t(TAG).d(errorCode + "," + result.getRealUrl()); + if (isIjk() && ijkPlayer != null) ijkPlayer.setMediaSource(IjkUtil.getSource(result)); + if (isExo() && exoPlayer != null) exoPlayer.setMediaSource(ExoUtil.getSource(result, errorCode)); + if (isExo() && exoPlayer != null) exoPlayer.prepare(); setTimeoutCheck(result.getRealUrl()); } private void setMediaSource(Channel channel) { - SpiderDebug.log(errorCode + "," + channel.getUrl()); - if (isIjk()) ijkPlayer.setMediaSource(IjkUtil.getSource(channel)); - if (isExo()) exoPlayer.setMediaSource(ExoUtil.getSource(channel, errorCode)); - if (isExo()) exoPlayer.prepare(); + Logger.t(TAG).d(errorCode + "," + channel.getUrl()); + if (isIjk() && ijkPlayer != null) ijkPlayer.setMediaSource(IjkUtil.getSource(channel)); + if (isExo() && exoPlayer != null) exoPlayer.setMediaSource(ExoUtil.getSource(channel, errorCode)); + if (isExo() && exoPlayer != null) exoPlayer.prepare(); setTimeoutCheck(channel.getUrl()); } private void setMediaSource(Map headers, String url) { - SpiderDebug.log(errorCode + "," + url); - if (isIjk()) ijkPlayer.setMediaSource(IjkUtil.getSource(headers, url)); - if (isExo()) exoPlayer.setMediaSource(ExoUtil.getSource(headers, url, errorCode)); - if (isExo()) exoPlayer.prepare(); + Logger.t(TAG).d(errorCode + "," + url); + if (isIjk() && ijkPlayer != null) ijkPlayer.setMediaSource(IjkUtil.getSource(headers, url)); + if (isExo() && exoPlayer != null) exoPlayer.setMediaSource(ExoUtil.getSource(headers, url, errorCode)); + if (isExo() && exoPlayer != null) exoPlayer.prepare(); setTimeoutCheck(url); } @@ -463,7 +475,8 @@ public class Players implements Player.Listener, IMediaPlayer.Listener, Analytic } private void setPlaybackState(int state) { - session.setPlaybackState(new PlaybackStateCompat.Builder().setState(state, getPosition(), getSpeed()).build()); + long actions = PlaybackStateCompat.ACTION_SEEK_TO | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; + session.setPlaybackState(new PlaybackStateCompat.Builder().setActions(actions).setState(state, getPosition(), getSpeed()).build()); } private boolean hasDanmu() { @@ -495,9 +508,8 @@ public class Players implements Player.Listener, IMediaPlayer.Listener, Analytic @Override public void onPlayerError(@NonNull PlaybackException error) { + ErrorEvent.format(ExoUtil.getRetry(errorCode = error.errorCode)); setPlaybackState(PlaybackStateCompat.STATE_ERROR); - this.errorCode = error.errorCode; - ErrorEvent.format(2); } @Override diff --git a/app/src/main/java/com/fongmi/android/tv/utils/M3U8.java b/app/src/main/java/com/fongmi/android/tv/utils/M3U8.java index 9be5dec64..ad723250a 100644 --- a/app/src/main/java/com/fongmi/android/tv/utils/M3U8.java +++ b/app/src/main/java/com/fongmi/android/tv/utils/M3U8.java @@ -1,10 +1,14 @@ package com.fongmi.android.tv.utils; +import android.net.Uri; + import androidx.media3.common.util.UriUtil; import com.github.catvod.net.OkHttp; import com.google.common.net.HttpHeaders; +import java.math.BigDecimal; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -13,16 +17,43 @@ import okhttp3.Headers; public class M3U8 { - private static final Pattern REGEX_URI = Pattern.compile("URI=\"(.+?)\""); + private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY"; + private static final String TAG_MEDIA_DURATION = "#EXTINF"; private static final String TAG_KEY = "#EXT-X-KEY"; + private static final Pattern REGEX_X_DISCONTINUITY = Pattern.compile("#EXT-X-DISCONTINUITY[\\s\\S]*?(?=#EXT-X-DISCONTINUITY|$)"); + private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION + ":([\\d\\.]+)\\b"); + private static final Pattern REGEX_URI = Pattern.compile("URI=\"(.+?)\""); + public static String get(String url, Map headers) throws Exception { String result = OkHttp.newCall(url, getHeader(headers)).execute().body().string(); Matcher matcher = Pattern.compile("#EXT-X-STREAM-INF(.*)\\n?(.*)").matcher(result); if (matcher.find() && matcher.groupCount() > 1) return get(UriUtil.resolve(url, matcher.group(2)), headers); StringBuilder sb = new StringBuilder(); for (String line : result.split("\n")) sb.append(shouldResolve(line) ? resolve(url, line) : line).append("\n"); - return sb.toString(); + List ads = Sniffer.getRegex(Uri.parse(url)); + return clean(sb.toString(), ads); + } + + private static String clean(String line, List ads) { + boolean scan = false; + for (String ad : ads) { + if (ad.contains(TAG_DISCONTINUITY) || ad.contains(TAG_MEDIA_DURATION)) line = line.replaceAll(ad, ""); + else if (isDouble(ad)) scan = true; + } + return scan ? scan(line, ads) : line; + } + + private static String scan(String line, List ads) { + Matcher m1 = REGEX_X_DISCONTINUITY.matcher(line); + while (m1.find()) { + String group = m1.group(); + BigDecimal t = BigDecimal.ZERO;; + Matcher m2 = REGEX_MEDIA_DURATION.matcher(group); + while (m2.find()) t = t.add(new BigDecimal(m2.group(1))); + for (String ad : ads) if (t.toString().startsWith(ad)) line = line.replaceAll(group, ""); + } + return line; } private static Headers getHeader(Map headers) { @@ -31,6 +62,14 @@ public class M3U8 { return builder.build(); } + private static boolean isDouble(String ad) { + try { + return Double.parseDouble(ad) > 0; + } catch (Exception e) { + return false; + } + } + private static boolean shouldResolve(String line) { return (!line.startsWith("#") && !line.startsWith("http")) || line.startsWith(TAG_KEY); } diff --git a/app/src/mobile/java/com/fongmi/android/tv/service/PlaybackService.java b/app/src/mobile/java/com/fongmi/android/tv/service/PlaybackService.java index 1796a2507..0496309f9 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/service/PlaybackService.java +++ b/app/src/mobile/java/com/fongmi/android/tv/service/PlaybackService.java @@ -1,10 +1,10 @@ package com.fongmi.android.tv.service; +import android.Manifest; import android.app.Notification; -import android.app.NotificationManager; import android.app.Service; -import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.os.IBinder; import android.support.v4.media.MediaMetadataCompat; @@ -12,7 +12,9 @@ import android.support.v4.media.MediaMetadataCompat; import androidx.annotation.DrawableRes; import androidx.annotation.Nullable; import androidx.annotation.StringRes; +import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.media.app.NotificationCompat.MediaStyle; @@ -27,6 +29,8 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import java.util.Objects; + public class PlaybackService extends Service { private static Players players; @@ -41,8 +45,16 @@ public class PlaybackService extends Service { App.get().stopService(new Intent(App.get(), PlaybackService.class)); } - private NotificationManager getManager() { - return (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); + private boolean isNull() { + return Objects.isNull(players) || Objects.isNull(players.getSession()); + } + + private boolean nonNull() { + return Objects.nonNull(players) && Objects.nonNull(players.getSession()); + } + + private NotificationManagerCompat getManager() { + return NotificationManagerCompat.from(this); } private NotificationCompat.Action buildNotificationAction(@DrawableRes int icon, @StringRes int title, String action) { @@ -50,12 +62,12 @@ public class PlaybackService extends Service { } private NotificationCompat.Action getPlayPauseAction() { - if (players != null && players.isPlaying()) return buildNotificationAction(androidx.media3.ui.R.drawable.exo_icon_pause, androidx.media3.ui.R.string.exo_controls_pause_description, ActionEvent.PAUSE); - return buildNotificationAction(androidx.media3.ui.R.drawable.exo_icon_play, androidx.media3.ui.R.string.exo_controls_play_description, ActionEvent.PLAY); + if (nonNull() && players.isPlaying()) return buildNotificationAction(R.drawable.ic_notify_pause, androidx.media3.ui.R.string.exo_controls_pause_description, ActionEvent.PAUSE); + return buildNotificationAction(R.drawable.ic_notify_play, androidx.media3.ui.R.string.exo_controls_play_description, ActionEvent.PLAY); } private MediaMetadataCompat getMetadata() { - return players.getSession().getController().getMetadata(); + return isNull() ? null : players.getSession().getController().getMetadata(); } private String getTitle() { @@ -80,14 +92,15 @@ public class PlaybackService extends Service { } private void setAction(NotificationCompat.Builder builder) { - builder.addAction(buildNotificationAction(androidx.media3.ui.R.drawable.exo_icon_previous, androidx.media3.ui.R.string.exo_controls_previous_description, ActionEvent.PREV)); + builder.addAction(buildNotificationAction(R.drawable.ic_notify_prev, androidx.media3.ui.R.string.exo_controls_previous_description, ActionEvent.PREV)); builder.addAction(getPlayPauseAction()); - builder.addAction(buildNotificationAction(androidx.media3.ui.R.drawable.exo_icon_next, androidx.media3.ui.R.string.exo_controls_next_description, ActionEvent.NEXT)); + builder.addAction(buildNotificationAction(R.drawable.ic_notify_next, androidx.media3.ui.R.string.exo_controls_next_description, ActionEvent.NEXT)); } private void setStyle(NotificationCompat.Builder builder) { MediaStyle style = new MediaStyle(); style.setShowCancelButton(true); + if (nonNull()) style.setMediaSession(players.getSession().getSessionToken()); style.setCancelButtonIntent(ActionReceiver.getPendingIntent(this, ActionEvent.STOP)); builder.setStyle(style); } @@ -106,12 +119,13 @@ public class PlaybackService extends Service { if (art != null) setLargeIcon(builder, art); builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); builder.setDeleteIntent(ActionReceiver.getPendingIntent(this, ActionEvent.STOP)); - builder.setContentIntent(players.getSession().getController().getSessionActivity()); + if (nonNull()) builder.setContentIntent(players.getSession().getController().getSessionActivity()); return builder.build(); } @Subscribe(threadMode = ThreadMode.MAIN) public void onActionEvent(ActionEvent event) { + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) return; if (event.isUpdate()) getManager().notify(ID, buildNotification()); } @@ -124,7 +138,7 @@ public class PlaybackService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { startForeground(ID, buildNotification()); - return START_STICKY; + return START_NOT_STICKY; } @Override