diff --git a/app/src/main/java/com/fongmi/bear/bean/Vod.java b/app/src/main/java/com/fongmi/bear/bean/Vod.java index 012af81e0..a6c264242 100644 --- a/app/src/main/java/com/fongmi/bear/bean/Vod.java +++ b/app/src/main/java/com/fongmi/bear/bean/Vod.java @@ -124,6 +124,7 @@ public class Vod { private final String name; private final String url; + private boolean activated; public Episode(String name, String url) { this.name = name; @@ -137,6 +138,14 @@ public class Vod { public String getUrl() { return url; } + + public boolean isActivated() { + return activated; + } + + public void setActivated(Episode item) { + this.activated = item.equals(this); + } } } } diff --git a/app/src/main/java/com/fongmi/bear/model/SiteViewModel.java b/app/src/main/java/com/fongmi/bear/model/SiteViewModel.java index 5da4b050b..decdb6031 100644 --- a/app/src/main/java/com/fongmi/bear/model/SiteViewModel.java +++ b/app/src/main/java/com/fongmi/bear/model/SiteViewModel.java @@ -9,6 +9,8 @@ import com.fongmi.bear.bean.Site; import com.fongmi.bear.bean.Vod; import com.github.catvod.crawler.Spider; import com.github.catvod.crawler.SpiderDebug; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import java.util.ArrayList; import java.util.HashMap; @@ -16,30 +18,27 @@ import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class SiteViewModel extends ViewModel { - public MutableLiveData mResult; - public ExecutorService mService; - - private enum Func { - HOME, CATEGORY, DETAIL, PLAYER, SEARCH - } + public MutableLiveData player; + public MutableLiveData result; + public ExecutorService service; public SiteViewModel() { - this.mService = Executors.newFixedThreadPool(2); - this.mResult = new MutableLiveData<>(); + this.service = Executors.newFixedThreadPool(2); + this.result = new MutableLiveData<>(); + this.player = new MutableLiveData<>(); } public MutableLiveData getResult() { - return mResult; + return result; } public void homeContent() { Site home = ApiConfig.get().getHome(); - postResult(Func.HOME, () -> { + postResult(() -> { Spider spider = ApiConfig.get().getCSP(home); String homeContent = spider.homeContent(false); SpiderDebug.json(homeContent); @@ -54,7 +53,7 @@ public class SiteViewModel extends ViewModel { public void categoryContent(String tid, String page, boolean filter, HashMap extend) { Site home = ApiConfig.get().getHome(); - postResult(Func.CATEGORY, () -> { + postResult(() -> { Spider spider = ApiConfig.get().getCSP(home); String categoryContent = spider.categoryContent(tid, page, filter, extend); SpiderDebug.json(categoryContent); @@ -64,42 +63,48 @@ public class SiteViewModel extends ViewModel { public void detailContent(String id) { Site home = ApiConfig.get().getHome(); - postResult(Func.DETAIL, () -> { + postResult(() -> { Spider spider = ApiConfig.get().getCSP(home); String detailContent = spider.detailContent(List.of(id)); SpiderDebug.json(detailContent); - return Result.objectFrom(detailContent); + Result result = Result.objectFrom(detailContent); + if (result.getList().isEmpty()) return result; + Vod vod = result.getList().get(0); + vod.setVodFlags(getVodFlags(vod)); + return result; }); } public void playerContent(String flag, String id) { Site home = ApiConfig.get().getHome(); - postResult(Func.PLAYER, () -> { + postPlayer(() -> { Spider spider = ApiConfig.get().getCSP(home); String playerContent = spider.playerContent(flag, id, ApiConfig.get().getFlags()); SpiderDebug.json(playerContent); - return Result.objectFrom(playerContent); + JsonObject object = JsonParser.parseString(playerContent).getAsJsonObject(); + if (!object.has("flag")) object.addProperty("flag", flag); + return object; }); } - private void postResult(Func func, Callable callable) { - mService.execute(() -> { + private void postResult(Callable callable) { + service.execute(() -> { try { - Future future = mService.submit(callable); - Result result = future.get(10, TimeUnit.SECONDS); - checkResult(func, result); - mResult.postValue(result); + result.postValue(service.submit(callable).get(10, TimeUnit.SECONDS)); } catch (Exception e) { - mResult.postValue(new Result()); + result.postValue(new Result()); } }); } - private void checkResult(Func func, Result result) { - if (func.equals(Func.DETAIL) && result.getList().size() > 0) { - Vod vod = result.getList().get(0); - vod.setVodFlags(getVodFlags(vod)); - } + private void postPlayer(Callable callable) { + service.execute(() -> { + try { + player.postValue(service.submit(callable).get(10, TimeUnit.SECONDS)); + } catch (Exception e) { + player.postValue(null); + } + }); } private List getVodFlags(Vod vod) { diff --git a/app/src/main/java/com/fongmi/bear/player/ExoUtil.java b/app/src/main/java/com/fongmi/bear/player/ExoUtil.java new file mode 100644 index 000000000..fe05cb7b9 --- /dev/null +++ b/app/src/main/java/com/fongmi/bear/player/ExoUtil.java @@ -0,0 +1,47 @@ +package com.fongmi.bear.player; + +import android.net.Uri; + +import com.fongmi.bear.App; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.ext.rtmp.RtmpDataSource; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.ProgressiveMediaSource; +import com.google.android.exoplayer2.source.dash.DashMediaSource; +import com.google.android.exoplayer2.source.hls.HlsMediaSource; +import com.google.android.exoplayer2.source.rtsp.RtspMediaSource; +import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultDataSource; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; +import com.google.android.exoplayer2.upstream.HttpDataSource; +import com.google.android.exoplayer2.util.Util; + +import java.util.Map; + +public class ExoUtil { + + public static MediaSource getSource(Map headers, String url) { + Uri videoUri = Uri.parse(url); + DataSource.Factory factory = getFactory(headers, url); + MediaItem mediaItem = new MediaItem.Builder().setUri(videoUri).build(); + int type = Util.inferContentType(videoUri); + if (type == C.CONTENT_TYPE_HLS || url.contains(".php") || url.contains(".m3u8")) { + return new HlsMediaSource.Factory(factory).createMediaSource(mediaItem); + } else if (type == C.CONTENT_TYPE_DASH) { + return new DashMediaSource.Factory(factory).createMediaSource(mediaItem); + } else if (type == C.CONTENT_TYPE_SS) { + return new SsMediaSource.Factory(factory).createMediaSource(mediaItem); + } else if (type == C.CONTENT_TYPE_RTSP) { + return new RtspMediaSource.Factory().createMediaSource(mediaItem); + } else { + return new ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem); + } + } + + private static DataSource.Factory getFactory(Map headers, String url) { + HttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory().setDefaultRequestProperties(headers).setAllowCrossProtocolRedirects(true); + return url.startsWith("rtmp") ? new RtmpDataSource.Factory() : new DefaultDataSource.Factory(App.get(), httpDataSourceFactory); + } +} diff --git a/app/src/main/java/com/fongmi/bear/player/Player.java b/app/src/main/java/com/fongmi/bear/player/Player.java new file mode 100644 index 000000000..b4803356b --- /dev/null +++ b/app/src/main/java/com/fongmi/bear/player/Player.java @@ -0,0 +1,37 @@ +package com.fongmi.bear.player; + +import com.fongmi.bear.App; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.util.HashMap; + +public class Player { + + private ExoPlayer mPlayer; + + private static class Loader { + static volatile Player INSTANCE = new Player(); + } + + public static Player get() { + return Loader.INSTANCE; + } + + public static ExoPlayer exo() { + return get().mPlayer = get().mPlayer == null ? new ExoPlayer.Builder(App.get()).build() : get().mPlayer; + } + + public void setMediaSource(JsonObject object) { + HashMap headers = new HashMap<>(); + String url = object.get("url").getAsString(); + if (object.has("header")) { + JsonObject header = JsonParser.parseString(object.get("header").getAsString()).getAsJsonObject(); + for (String key : header.keySet()) headers.put(key, header.get(key).getAsString()); + } + mPlayer.setMediaSource(ExoUtil.getSource(headers, url)); + mPlayer.prepare(); + mPlayer.play(); + } +} diff --git a/app/src/main/java/com/fongmi/bear/ui/activity/DetailActivity.java b/app/src/main/java/com/fongmi/bear/ui/activity/DetailActivity.java index 99a9aa7bf..58e59482a 100644 --- a/app/src/main/java/com/fongmi/bear/ui/activity/DetailActivity.java +++ b/app/src/main/java/com/fongmi/bear/ui/activity/DetailActivity.java @@ -20,6 +20,7 @@ import com.fongmi.bear.R; import com.fongmi.bear.bean.Vod; import com.fongmi.bear.databinding.ActivityDetailBinding; import com.fongmi.bear.model.SiteViewModel; +import com.fongmi.bear.player.Player; import com.fongmi.bear.ui.presenter.EpisodePresenter; import com.fongmi.bear.ui.presenter.FlagPresenter; import com.fongmi.bear.ui.presenter.GroupPresenter; @@ -31,11 +32,11 @@ import java.util.List; public class DetailActivity extends BaseActivity { private ActivityDetailBinding mBinding; - private SiteViewModel mSiteViewModel; private ArrayObjectAdapter mFlagAdapter; - private ArrayObjectAdapter mEpisodeAdapter; private ArrayObjectAdapter mGroupAdapter; + private ArrayObjectAdapter mEpisodeAdapter; private EpisodePresenter mEpisodePresenter; + private SiteViewModel mSiteViewModel; private View mOldView; private String getId() { @@ -56,6 +57,7 @@ public class DetailActivity extends BaseActivity { @Override protected void initView() { mBinding.progress.showProgress(); + mBinding.video.setPlayer(Player.exo()); setRecyclerView(); setViewModel(); getDetail(); @@ -63,7 +65,29 @@ public class DetailActivity extends BaseActivity { @Override protected void initEvent() { - + mBinding.flag.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { + @Override + public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { + if (mOldView != null) mOldView.setActivated(false); + if (child == null) return; + mOldView = child.itemView; + mOldView.setActivated(true); + setEpisode((Vod.Flag) mFlagAdapter.get(position)); + } + }); + mBinding.group.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { + @Override + public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { + if (mEpisodeAdapter.size() > 20) { + mBinding.episode.setSelectedPosition(position * 20); + } + } + }); + mEpisodePresenter.setOnClickListener(item -> { + for (int i = 0; i < mEpisodeAdapter.size(); i++) ((Vod.Flag.Episode) mEpisodeAdapter.get(i)).setActivated(item); + mEpisodeAdapter.notifyArrayItemRangeChanged(0, mEpisodeAdapter.size()); + getPlayer(mEpisodePresenter.getFlag(), item.getUrl()); + }); } private void setRecyclerView() { @@ -76,22 +100,6 @@ public class DetailActivity extends BaseActivity { mBinding.group.setHorizontalSpacing(ResUtil.dp2px(8)); mBinding.group.setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); mBinding.group.setAdapter(new ItemBridgeAdapter(mGroupAdapter = new ArrayObjectAdapter(new GroupPresenter()))); - mBinding.group.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { - @Override - public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { - if (mEpisodeAdapter.size() > 20) mBinding.episode.setSelectedPosition(position * 20); - } - }); - mBinding.flag.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { - @Override - public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { - if (mOldView != null) mOldView.setActivated(false); - if (child == null) return; - mOldView = child.itemView; - mOldView.setActivated(true); - setEpisode((Vod.Flag) mFlagAdapter.get(position)); - } - }); } private void getDetail() { @@ -104,7 +112,8 @@ public class DetailActivity extends BaseActivity { private void setViewModel() { mSiteViewModel = new ViewModelProvider(this).get(SiteViewModel.class); - mSiteViewModel.mResult.observe(this, result -> { + mSiteViewModel.player.observe(this, object -> Player.get().setMediaSource(object)); + mSiteViewModel.result.observe(this, result -> { if (result.getList().isEmpty()) mBinding.progress.showErrorText(); else setDetail(result.getList().get(0)); }); @@ -130,6 +139,7 @@ public class DetailActivity extends BaseActivity { private void setEpisode(Vod.Flag item) { mEpisodeAdapter.clear(); + mEpisodePresenter.setFlag(item.getFlag()); mEpisodeAdapter.addAll(0, item.getEpisodes()); if (item.getEpisodes().size() > 20) setGroup(item.getEpisodes().size()); } diff --git a/app/src/main/java/com/fongmi/bear/ui/activity/HomeActivity.java b/app/src/main/java/com/fongmi/bear/ui/activity/HomeActivity.java index ed4758f03..05bbfcc26 100644 --- a/app/src/main/java/com/fongmi/bear/ui/activity/HomeActivity.java +++ b/app/src/main/java/com/fongmi/bear/ui/activity/HomeActivity.java @@ -69,7 +69,7 @@ public class HomeActivity extends BaseActivity implements VodPresenter.OnClickLi private void setViewModel() { mSiteViewModel = new ViewModelProvider(this).get(SiteViewModel.class); - mSiteViewModel.mResult.observe(this, result -> { + mSiteViewModel.result.observe(this, result -> { mAdapter.remove("progress"); for (List items : result.partition()) { VodPresenter presenter = new VodPresenter(items.size()); diff --git a/app/src/main/java/com/fongmi/bear/ui/fragment/VodFragment.java b/app/src/main/java/com/fongmi/bear/ui/fragment/VodFragment.java index a36b593ec..7f31ff5bb 100644 --- a/app/src/main/java/com/fongmi/bear/ui/fragment/VodFragment.java +++ b/app/src/main/java/com/fongmi/bear/ui/fragment/VodFragment.java @@ -88,7 +88,7 @@ public class VodFragment extends Fragment implements Scroller.Callback, VodPrese private void setViewModel() { mSiteViewModel = new ViewModelProvider(this).get(SiteViewModel.class); - mSiteViewModel.mResult.observe(getViewLifecycleOwner(), result -> { + mSiteViewModel.result.observe(getViewLifecycleOwner(), result -> { mAdapter.remove("progress"); mScroller.endLoading(result.getList().isEmpty()); for (List items : result.partition()) { diff --git a/app/src/main/java/com/fongmi/bear/ui/presenter/EpisodePresenter.java b/app/src/main/java/com/fongmi/bear/ui/presenter/EpisodePresenter.java index a904bda72..8e0a69d96 100644 --- a/app/src/main/java/com/fongmi/bear/ui/presenter/EpisodePresenter.java +++ b/app/src/main/java/com/fongmi/bear/ui/presenter/EpisodePresenter.java @@ -12,6 +12,7 @@ import com.fongmi.bear.databinding.AdapterEpisodeBinding; public class EpisodePresenter extends Presenter { private OnClickListener mListener; + private String flag; public interface OnClickListener { void onItemClick(Vod.Flag.Episode item); @@ -21,6 +22,14 @@ public class EpisodePresenter extends Presenter { this.mListener = listener; } + public String getFlag() { + return flag; + } + + public void setFlag(String flag) { + this.flag = flag; + } + @Override public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { return new ViewHolder(AdapterEpisodeBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); @@ -31,6 +40,8 @@ public class EpisodePresenter extends Presenter { Vod.Flag.Episode item = (Vod.Flag.Episode) object; ViewHolder holder = (ViewHolder) viewHolder; holder.binding.text.setText(item.getName()); + holder.binding.text.setActivated(item.isActivated()); + setOnClickListener(holder, view -> mListener.onItemClick(item)); } @Override diff --git a/app/src/main/res/layout/activity_detail.xml b/app/src/main/res/layout/activity_detail.xml index 5536013e6..79b3fa229 100644 --- a/app/src/main/res/layout/activity_detail.xml +++ b/app/src/main/res/layout/activity_detail.xml @@ -1,5 +1,6 @@ + android:focusableInTouchMode="true" + app:use_controller="false" />