mirror of
https://framagit.org/tom79/nitterizeme
synced 2025-02-03 02:37:48 +01:00
Built in player
This commit is contained in:
parent
1632b6f57e
commit
dccfd0e1ae
@ -29,8 +29,10 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'org.jsoup:jsoup:1.13.1'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
|
@ -182,6 +182,11 @@
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity android:name="app.fedilab.nitterizeme.WebviewPlayerActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,182 @@
|
||||
package app.fedilab.nitterizeme;
|
||||
/* Copyright 2020 Thomas Schneider
|
||||
*
|
||||
* This file is a part of NitterizeMe
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* NitterizeMe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with NitterizeMe; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.app.Activity;
|
||||
import android.media.MediaPlayer;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
/**
|
||||
* Custom WebChromeClient
|
||||
*/
|
||||
|
||||
public class PlayerChromeClient extends WebChromeClient implements MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
|
||||
|
||||
private FrameLayout videoViewContainer;
|
||||
private CustomViewCallback videoViewCallback;
|
||||
|
||||
private ToggledFullscreenCallback toggledFullscreenCallback;
|
||||
|
||||
private WebView webView;
|
||||
private View activityNonVideoView;
|
||||
private ViewGroup activityVideoView;
|
||||
private boolean isVideoFullscreen;
|
||||
private Activity activity;
|
||||
|
||||
|
||||
PlayerChromeClient(Activity activity, WebView webView, FrameLayout activityNonVideoView, ViewGroup activityVideoView) {
|
||||
this.activity = activity;
|
||||
this.isVideoFullscreen = false;
|
||||
this.webView = webView;
|
||||
this.activityNonVideoView = activityNonVideoView;
|
||||
this.activityVideoView = activityVideoView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback that will be fired when the video starts or finishes displaying using a custom view (typically full-screen)
|
||||
*
|
||||
* @param callback A VideoEnabledWebChromeClient.ToggledFullscreenCallback callback
|
||||
*/
|
||||
void setOnToggledFullscreen(ToggledFullscreenCallback callback) {
|
||||
this.toggledFullscreenCallback = callback;
|
||||
}
|
||||
|
||||
//FULLSCREEN VIDEO
|
||||
//Code from https://stackoverflow.com/a/16179544/3197259
|
||||
|
||||
@Override
|
||||
public void onShowCustomView(View view, CustomViewCallback callback) {
|
||||
if (view instanceof FrameLayout) {
|
||||
if (((AppCompatActivity) activity).getSupportActionBar() != null)
|
||||
//noinspection ConstantConditions
|
||||
((AppCompatActivity) activity).getSupportActionBar().hide();
|
||||
// A video wants to be shown
|
||||
FrameLayout frameLayout = (FrameLayout) view;
|
||||
View focusedChild = frameLayout.getFocusedChild();
|
||||
|
||||
// Save video related variables
|
||||
isVideoFullscreen = true;
|
||||
this.videoViewContainer = frameLayout;
|
||||
this.videoViewCallback = callback;
|
||||
|
||||
// Hide the non-video view, add the video view, and show it
|
||||
activityNonVideoView.setVisibility(View.INVISIBLE);
|
||||
activityVideoView.addView(videoViewContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
activityVideoView.setVisibility(View.VISIBLE);
|
||||
if (focusedChild instanceof android.widget.VideoView) {
|
||||
// android.widget.VideoView (typically API level <11)
|
||||
android.widget.VideoView videoView = (android.widget.VideoView) focusedChild;
|
||||
// Handle all the required events
|
||||
videoView.setOnCompletionListener(this);
|
||||
videoView.setOnErrorListener(this);
|
||||
} else {
|
||||
// Other classes, including:
|
||||
// - android.webkit.HTML5VideoFullScreen$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 11-18)
|
||||
// - android.webkit.HTML5VideoFullScreen$VideoTextureView, which inherits from android.view.TextureView (typically API level 11-18)
|
||||
// - com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 19+)
|
||||
|
||||
// Handle HTML5 video ended event only if the class is a SurfaceView
|
||||
// Test case: TextureView of Sony Xperia T API level 16 doesn't work fullscreen when loading the javascript below
|
||||
if (webView != null && webView.getSettings().getJavaScriptEnabled() && focusedChild instanceof SurfaceView) {
|
||||
// Run javascript code that detects the video end and notifies the Javascript interface
|
||||
String js = "javascript:";
|
||||
js += "var _ytrp_html5_video_last;";
|
||||
js += "var _ytrp_html5_video = document.getElementsByTagName('video')[0];";
|
||||
js += "if (_ytrp_html5_video != undefined && _ytrp_html5_video != _ytrp_html5_video_last) {";
|
||||
{
|
||||
js += "_ytrp_html5_video_last = _ytrp_html5_video;";
|
||||
js += "function _ytrp_html5_video_ended() {";
|
||||
{
|
||||
js += "_VideoEnabledWebView.notifyVideoEnd();"; // Must match Javascript interface name and method of VideoEnableWebView
|
||||
}
|
||||
js += "}";
|
||||
js += "_ytrp_html5_video.addEventListener('ended', _ytrp_html5_video_ended);";
|
||||
}
|
||||
js += "}";
|
||||
webView.loadUrl(js);
|
||||
}
|
||||
}
|
||||
// Notify full-screen change
|
||||
if (toggledFullscreenCallback != null) {
|
||||
toggledFullscreenCallback.toggledFullscreen(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Available in API level 14+, deprecated in API level 18+
|
||||
@Override
|
||||
public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
|
||||
onShowCustomView(view, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHideCustomView() {
|
||||
if (((AppCompatActivity) activity).getSupportActionBar() != null)
|
||||
//noinspection ConstantConditions
|
||||
((AppCompatActivity) activity).getSupportActionBar().show();
|
||||
// This method should be manually called on video end in all cases because it's not always called automatically.
|
||||
// This method must be manually called on back key press (from this class' onBackPressed() method).
|
||||
if (isVideoFullscreen) {
|
||||
// Hide the video view, remove it, and show the non-video view
|
||||
activityVideoView.setVisibility(View.INVISIBLE);
|
||||
activityVideoView.removeView(videoViewContainer);
|
||||
activityNonVideoView.setVisibility(View.VISIBLE);
|
||||
// Call back (only in API level <19, because in API level 19+ with chromium webview it crashes)
|
||||
if (videoViewCallback != null && !videoViewCallback.getClass().getName().contains(".chromium.")) {
|
||||
videoViewCallback.onCustomViewHidden();
|
||||
}
|
||||
|
||||
// Reset video related variables
|
||||
isVideoFullscreen = false;
|
||||
videoViewContainer = null;
|
||||
videoViewCallback = null;
|
||||
|
||||
// Notify full-screen change
|
||||
if (toggledFullscreenCallback != null) {
|
||||
toggledFullscreenCallback.toggledFullscreen(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Video will start loading
|
||||
@Override
|
||||
public View getVideoLoadingProgressView() {
|
||||
return super.getVideoLoadingProgressView();
|
||||
}
|
||||
|
||||
// Video finished playing, only called in the case of android.widget.VideoView (typically API level <11)
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mp) {
|
||||
onHideCustomView();
|
||||
}
|
||||
|
||||
// Error while playing video, only called in the case of android.widget.VideoView (typically API level <11)
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||
return false; // By returning false, onCompletion() will be called
|
||||
}
|
||||
|
||||
public interface ToggledFullscreenCallback {
|
||||
void toggledFullscreen(boolean fullscreen);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
@ -420,7 +421,20 @@ public class TransformActivity extends Activity {
|
||||
targetIntent.setComponent(new ComponentName("org.schabi.newpipe", "org.schabi.newpipe.RouterActivity"));
|
||||
targetIntents.add(targetIntent);
|
||||
}
|
||||
if (targetIntents.size() > 0) {
|
||||
|
||||
if (Arrays.asList(invidious_instances).contains(Objects.requireNonNull(i.getData()).getHost())) {
|
||||
if( !i.getData().toString().contains("videoplayback")){
|
||||
Intent intentPlayer = new Intent(TransformActivity.this, WebviewPlayerActivity.class);
|
||||
intentPlayer.putExtra("url", i.getData().toString());
|
||||
startActivity(intentPlayer);
|
||||
}else{
|
||||
Intent intentStreamingUrl = new Intent(Utils.RECEIVE_STREAMING_URL);
|
||||
Bundle b = new Bundle();
|
||||
b.putString("streaming_url", i.getData().toString());
|
||||
intentStreamingUrl.putExtras(b);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intentStreamingUrl);
|
||||
}
|
||||
}else if (targetIntents.size() > 0) {
|
||||
Intent chooserIntent = Intent.createChooser(targetIntents.remove(0), getString(R.string.open_with));
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetIntents.toArray(new Parcelable[]{}));
|
||||
startActivity(chooserIntent);
|
||||
|
@ -31,6 +31,8 @@ import static app.fedilab.nitterizeme.MainActivity.shortener_domains;
|
||||
|
||||
class Utils {
|
||||
|
||||
public static final String RECEIVE_STREAMING_URL = "receive_streaming_url";
|
||||
|
||||
private static final String[] UTM_PARAMS = {
|
||||
"utm_\\w+",
|
||||
"ga_source",
|
||||
|
@ -0,0 +1,146 @@
|
||||
package app.fedilab.nitterizeme;
|
||||
/* Copyright 2020 Thomas Schneider
|
||||
*
|
||||
* This file is a part of NitterizeMe
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* NitterizeMe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with NitterizeMe; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import java.io.IOException;
|
||||
|
||||
public class WebviewPlayerActivity extends AppCompatActivity {
|
||||
|
||||
private String videoUrl;
|
||||
private WebView webView;
|
||||
private BroadcastReceiver receive_data;
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String url = null;
|
||||
Bundle b = getIntent().getExtras();
|
||||
if (b != null) {
|
||||
url = b.getString("url", null);
|
||||
}
|
||||
if( url == null){
|
||||
finish();
|
||||
}
|
||||
setContentView(R.layout.activity_webview_player);
|
||||
|
||||
final ViewGroup videoLayout = findViewById(R.id.videoLayout);
|
||||
webView = findViewById(R.id.webview);
|
||||
FrameLayout webview_container = findViewById(R.id.webview_container);
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
|
||||
PlayerChromeClient playerChromeClient = new PlayerChromeClient(WebviewPlayerActivity.this, webView, webview_container, videoLayout);
|
||||
playerChromeClient.setOnToggledFullscreen(fullscreen -> {
|
||||
if (fullscreen) {
|
||||
videoLayout.setVisibility(View.VISIBLE);
|
||||
WindowManager.LayoutParams attrs = getWindow().getAttributes();
|
||||
attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
||||
attrs.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
|
||||
getWindow().setAttributes(attrs);
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
|
||||
} else {
|
||||
WindowManager.LayoutParams attrs = getWindow().getAttributes();
|
||||
attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
||||
attrs.flags &= ~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
|
||||
getWindow().setAttributes(attrs);
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
||||
videoLayout.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
receive_data = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Bundle b = intent.getExtras();
|
||||
assert b != null;
|
||||
String streaming_url = b.getString("streaming_url", null);
|
||||
if (streaming_url != null ) {
|
||||
webView.stopLoading();
|
||||
webView.loadUrl(streaming_url);
|
||||
WindowManager.LayoutParams attrs = getWindow().getAttributes();
|
||||
attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
||||
attrs.flags &= ~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
|
||||
getWindow().setAttributes(attrs);
|
||||
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
|
||||
videoLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
};
|
||||
webView.setWebChromeClient(playerChromeClient);
|
||||
LocalBroadcastManager.getInstance(WebviewPlayerActivity.this).registerReceiver(receive_data, new IntentFilter(Utils.RECEIVE_STREAMING_URL));
|
||||
|
||||
String finalUrl = url;
|
||||
AsyncTask.execute(() -> {
|
||||
try {
|
||||
Document document = Jsoup
|
||||
.connect(finalUrl).ignoreContentType(true).get();
|
||||
|
||||
Element video = document.select("video").first();
|
||||
if( video != null ){
|
||||
Element source = video.select("source").first();
|
||||
if( source != null ) {
|
||||
videoUrl = source.absUrl("src");
|
||||
runOnUiThread(() -> {
|
||||
webView.loadUrl(videoUrl);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause(){
|
||||
super.onPause();
|
||||
if( webView != null ){
|
||||
webView.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
if( webView != null ){
|
||||
webView.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
super.onDestroy();
|
||||
if (receive_data != null)
|
||||
LocalBroadcastManager.getInstance(WebviewPlayerActivity.this).unregisterReceiver(receive_data);
|
||||
}
|
||||
}
|
41
app/src/main/res/layout/activity_webview_player.xml
Normal file
41
app/src/main/res/layout/activity_webview_player.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
/* Copyright 2020 Thomas Schneider
|
||||
*
|
||||
* This file is a part of NitterizeMe
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* NitterizeMe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with NitterizeMe; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
-->
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".WebviewPlayerActivity">
|
||||
<FrameLayout
|
||||
android:id="@+id/webview_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
<WebView
|
||||
android:background="@android:color/black"
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
/>
|
||||
</FrameLayout>
|
||||
<!-- View where the video will be shown when video goes fullscreen -->
|
||||
<RelativeLayout
|
||||
android:id="@+id/videoLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
</RelativeLayout>
|
@ -258,6 +258,17 @@
|
||||
app:layout_constraintStart_toEndOf="@id/label_instance_invidious"
|
||||
app:layout_constraintTop_toTopOf="@id/guide_invidious" />
|
||||
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/enable_embed_player"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:text="@string/use_built_in_player"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/barrier_invidious" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier_invidious"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -52,4 +52,5 @@
|
||||
<string name="instance_info">Information about listed instances</string>
|
||||
<string name="about_instances">The list of instances comes from\n\n <a href="%1$s">%2$s</a>\n\nYou can contribute to this list by adding or removing instances.</string>
|
||||
<string name="about_instances_title">About instances</string>
|
||||
<string name="use_built_in_player">Use the built-in player</string>
|
||||
</resources>
|
||||
|
@ -40,4 +40,12 @@
|
||||
<item name="cardCornerRadius">5dp</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="Theme.AppCompat.Light.NoActionBar.FullScreen" parent="@style/Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user