Support simple m3u8 play

pull/3/head
FongMi 4 years ago
parent edc88d2f7a
commit ccf6d607ae
  1. 9
      app/src/main/java/com/fongmi/bear/bean/Vod.java
  2. 61
      app/src/main/java/com/fongmi/bear/model/SiteViewModel.java
  3. 47
      app/src/main/java/com/fongmi/bear/player/ExoUtil.java
  4. 37
      app/src/main/java/com/fongmi/bear/player/Player.java
  5. 50
      app/src/main/java/com/fongmi/bear/ui/activity/DetailActivity.java
  6. 2
      app/src/main/java/com/fongmi/bear/ui/activity/HomeActivity.java
  7. 2
      app/src/main/java/com/fongmi/bear/ui/fragment/VodFragment.java
  8. 11
      app/src/main/java/com/fongmi/bear/ui/presenter/EpisodePresenter.java
  9. 4
      app/src/main/res/layout/activity_detail.xml

@ -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);
}
}
}
}

@ -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<Result> mResult;
public ExecutorService mService;
private enum Func {
HOME, CATEGORY, DETAIL, PLAYER, SEARCH
}
public MutableLiveData<JsonObject> player;
public MutableLiveData<Result> 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<Result> 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<String, String> 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<Result> callable) {
mService.execute(() -> {
private void postResult(Callable<Result> callable) {
service.execute(() -> {
try {
Future<Result> 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<JsonObject> callable) {
service.execute(() -> {
try {
player.postValue(service.submit(callable).get(10, TimeUnit.SECONDS));
} catch (Exception e) {
player.postValue(null);
}
});
}
private List<Vod.Flag> getVodFlags(Vod vod) {

@ -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<String, String> 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<String, String> 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);
}
}

@ -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<String, String> 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();
}
}

@ -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());
}

@ -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<Vod> items : result.partition()) {
VodPresenter presenter = new VodPresenter(items.size());

@ -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<Vod> items : result.partition()) {

@ -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

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<com.fongmi.bear.ui.custom.ProgressLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/progress"
android:layout_width="match_parent"
@ -14,7 +15,8 @@
android:layout_marginEnd="24dp"
android:layout_marginBottom="12dp"
android:focusable="true"
android:focusableInTouchMode="true" />
android:focusableInTouchMode="true"
app:use_controller="false" />
<TextView
android:id="@+id/name"

Loading…
Cancel
Save