I'm using React Native to create an Android/iOS app and trying to get a video to play in the WebView component. The video plays fine on iOS, but I'm having trouble getting it to play in the android WebView.
I've come across a few threads like this one (Enabling HTML5 video playback in android WebView?) that claim this is a fairly common problem on Android and can be solved by importing WebChromeClient and setting that option on the webview like so:
mainWebView.setWebChromeClient(new WebChromeClient());
But almost all these threads are strictly about building a native Android app and not using React Native.
Does anyone know how to get this to work in React Native?
I refer to an article by Yevgen Safronov
In it, he writes
Obviously the most challenging part of the application is handling
live video stream, because it requires switching stream’s video
quality based on available Internet bandwidth. But first things
first — I needed a RN native component to show any video stream. There
is a popular video component for RN but it has support for iOS only. I
decided to write my own RN component wrapper around Vitamio player. It
is well known open-source project and has support of RTMP protocol we
use for mobile app.
I had no prior experience with writing native RN components so I went
directly to RN documentation on how to create one. A guide I refer to
is called Native UI Components, there is similar one for iOS. There
are several essential parts to declare:
Implement custom ViewManager (Android part)
Register the ViewManager (Android part)
Implement the JavaScript module
Register the module (Android part)
Implement custom ViewManager Referring to the example of declaring
VideoView for Vitamio this is how the essence of VideoView declaration
looks like:
public class VideoViewDemo extends Activity {
#Override public void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (!LibsChecker.checkVitamioLibs(this))
return;
setContentView(R.layout.videoview);
mEditText = (EditText) findViewById(R.id.url);
mVideoView = (VideoView) findViewById(R.id.surface_view);
if (path == "") { return; }
mVideoView.setVideoPath(path);
mVideoView.setMediaController(new MediaController(this));
mVideoView.requestFocus();
}
...
}
The code looks quite straightforward. Apart from passing a reference
to Activity into LibsChecker, VideoView requires a path to a video
stream and instance of MediaController.
public class VitamioViewManager extends SimpleViewManager<VideoView>{
public static final String REACT_CLASS = “RCTVitamioView”;
#Override
public String getName() {
return REACT_CLASS;
}
expose setStreamUrl setter using ReactProp:
#ReactProp(name = "streamUrl")
public void setStreamUrl(VideoView view, #Nullable String streamUrl) {
if (!LibsChecker.checkVitamioLibs(mActivity))
return;
view.setVideoPath(streamUrl);
view.setMediaController(new MediaController(mContext));
view.requestFocus();
}
add createViewInstance implementation:
private ThemedReactContext mContext = null;
private Activity mActivity = null;
#Override
public VideoView createViewInstance(ThemedReactContext context){
mContext = context;
return new VideoView(context);
}
One note about the code. Because LibsChecker requires an instance of Activity we will receive it via constructor, it will reference root activity used for RN application;
public VitamioViewManager(Activity activity) {
mActivity = activity;
}
Register the ViewManager
The final Java step is to register the ViewManager to the application, this happens via the applications package member function createViewManagers:
...
public class VitamioViewPackage implements ReactPackage {
private Activity mActivity = null;
public VitamioViewPackage(Activity activity) {
mActivity = activity;
}
#Override
public List<NativeModule>
createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
#Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
#Override
public List<ViewManager>
createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new VitamioViewManager(mActivity)
);
}
}
Implement the JavaScript module In order to expose custom UI component
in JavaScript it is necessary to call special requireNativeComponent
function:
var { requireNativeComponent, PropTypes } = require('react-native');
var iface = {
name: 'VideoView',
propTypes: {
streamUrl: PropTypes.string
}
};
module.exports = requireNativeComponent('RCTVitamioView', iface);
Register the module Although it’s not mentioned as required step in
official documentation we need it because of reference to the root
activity: package com.vitamio_demo;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import java.util.Arrays;
import java.util.List;
import com.sejoker.VitamView.VitamioViewPackage; // <--- import
public class MainActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
#Override
protected String getMainComponentName() {
return "vitamio_demo";
}
/**
* Returns whether dev mode should be enabled.
* This enables e.g. the dev menu.
*/
#Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
/**
* A list of packages used by the app. If the app uses additional views
* or modules besides the default ones, add more packages here.
*/
#Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new VitamioViewPackage(this) // <------ add here
);
}
}
Example of usage
Install the package in a project:
npm i react-native-android-vitamio --save
DeclareVideoView:
var VitamioView = require('react-native-android-vitamio');
class VideoScreen extends React.Component {
render() {
return (
<View>
<VitamioView style={styles.video} streamUrl="rtmp://fms.12E5.edgecastcdn.net/0012E5/mp4:videos/8Juv1MVa-485.mp4"/>
</View>
);
}
}
var styles = StyleSheet.create({
video: {
flex: 1,
flexDirection: 'row',
height: 400,
}
})
module.exports = VideoScreen;
Hope this is of help, A list of his own references is given in the article.
Related
Everything was working until two days ago I got this error:
> Task :app:compileDebugJavaWithJavac FAILED
Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/5.5/userguide/command_line_interface.html#sec:command_line_warnings
224 actionable tasks: 195 executed, 29 up-to-date
Note: /Users/yashatreya/Desktop/realyze/node_modules/#react-native-community/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/AsyncStorage
Module.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: /Users/yashatreya/Desktop/realyze/node_modules/#react-native-community/datetimepicker/android/src/main/java/com/reactcommunity/rndatetimepicker/RNDatePickerD
ialogFragment.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Note: /Users/yashatreya/Desktop/realyze/node_modules/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: Some input files use unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Note: Some input files use or override a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: /Users/yashatreya/Desktop/realyze/node_modules/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NodesManager.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: /Users/yashatreya/Desktop/realyze/node_modules/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NodesManager.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Note: /Users/yashatreya/Desktop/realyze/node_modules/react-native-sound/android/src/main/java/com/zmxv/RNSound/RNSoundModule.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: /Users/yashatreya/Desktop/realyze/node_modules/react-native-svg/android/src/main/java/com/horcrux/svg/TSpanView.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
/Users/yashatreya/Desktop/realyze/android/app/build/generated/rncli/src/main/java/debug/facebook/react/PackageList.java:90: error: getApplication() has protected access in ReactNativeHost
return this.reactNativeHost.getApplication();
^
1 error
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.
This is my MainApplication.java
package com.realyze;
import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import io.expo.appearance.RNCAppearancePackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
//MultiDex
import androidx.multidex.MultiDexApplication;
//RN-PushNotification
import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage;
public class MainApplication extends MultiDexApplication implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
#Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
#Override
protected List<ReactPackage> getPackages() {
#SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
new ReactNativePushNotificationPackage();
return packages;
}
#Override
protected String getJSMainModuleName() {
return "index";
}
};
#Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
#Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this); // Remove this line if you don't want Flipper enabled
}
/**
* Loads Flipper in React Native templates.
*
* #param context
*/
private static void initializeFlipper(Context context) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("com.facebook.flipper.ReactNativeFlipper");
aClass.getMethod("initializeFlipper", Context.class).invoke(null, context);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
Versions:
React Native: 0.61.4
Android Studio: 3.6.1
Gradle Plugin: 3.4.2
I have tried clearing the build cache but it still gives me the same error.
I am a beginner react-native developer and I can't understand what is going wrong.
Your help would be very much appreciated
The comments inside MainApplication.java are there to help and guide you on how to add packages that do not get to auto-link; so, if react-native-push-notification does not support auto-linking, you'll have to add it to the packages list like this:
//RN-PushNotification
import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage;
public class MainApplication extends MultiDexApplication implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
#Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
#Override
protected List<ReactPackage> getPackages() {
#SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// Add a new ReactNativePushNotificationPackage to the packages
// ONLY IF ReactNativePushNotificationPackage does not support auto-linking
packages.add(new ReactNativePushNotificationPackage());
return packages;
}
...
If it does support auto-linking, then you'll have to remove/reverse the manual installation steps, clean the project and let React Native handle the native linking with the new RN >= 60 auto-linking system.
I have an app with the following code on the MainActivity.java:
package com.MyApp;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import android.view.Window;
import android.view.WindowManager;
import android.os.Bundle;
import com.MyApp.CustomLockscreenPackage;
public class MainActivity extends ReactActivity {
#Override
protected String getMainComponentName() {
return "MyApp";
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Window w = getWindow();
w.setFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
);
}
#Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
#Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
};
}
}
The lines who make the app work on the lockscreen are:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Window w = getWindow();
w.setFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
);
}
But, the entire app works on the lockscreen, and i need to make only certain screens works on the lockscreen (It's a call app).
I tried creating a native module but don't worked.
i know its possible remove the flags with:
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
So, if i can call these lines on my react native screen, it probably can work
[EDIT]: I made a module who does that for me: react-native-keep-awake
[EDIT]: How to use my module:
first of all, install him:
npm install --save https://github.com/willnaoosmit/react-native-keep-visible
(or with yarn, if you want to)
then:
import KeepAwake from 'react-native-keep-awake'; #import the module, of course
KeepAwake.activate(); #when you want to use the app on the lockscreen
KeepAwake.deactivate(); #when you dont want to use the app on the lockscreen anymore
I am trying to set up a simple Android app that displays a webview and has some extra functionality available via the Javascript Interface.
I am testing on a HTC One S running Kitkat (via Cyanogenmod). When I inspect the webview the object inserted (Android) is empty "Object {}". I cannot use the functions in the interface via the loaded page.
I had built a more complicated activity but could not get the interface working, so I've created this new app for testing which contains the only the code from the webview guides. Still not inserting the methods into the exposed object.
MyActivity
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.webkit.WebSettings;
import android.webkit.WebView;
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.addJavascriptInterface(new WebAppInterface(this), "Android");
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
myWebView.loadUrl("http://47e148ba.ngrok.com");
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.my, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
WebAppInterface
import android.content.Context;
import android.webkit.JavascriptInterface;
import android.widget.Toast;
/**
* Created by graeme on 28/08/2014.
*/
public class WebAppInterface {
Context mContext;
/** Instantiate the interface and set the context */
WebAppInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
#JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
Layout
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 19
buildToolsVersion "20.0.0"
defaultConfig {
applicationId "com.mydomain.jswebview"
minSdkVersion 17
targetSdkVersion 19
versionCode 1
versionName "1.0"
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
You can see I'm using the decorator required for the methods, which most questions seem to have as the solution. I'm developing in Android Studio if any of the presets in there might be interfering?
Edit:
In The JS file, or via the console if I'm inspecting the webview via chrome if I call Android or window.Android i get an empty objected returned "Object {}". Or if I try to use the methods defined in the interface above I'll receive a method undefined error.
I was having the same problem. Turned out to be a Proguard problem. Try adding the following to your Proguard rules:
-keepclassmembers class ** {
#android.webkit.JavascriptInterface <methods>;
}
I don't know how are you trying to call to your Java methods from inside of your WebView. I think that the correct way is:
javascript:window.Android.showToast('Hi!');
you could also test your JS interface from your Java code using:
myWebView.loadUrl("javascript:window.Android.showToast('Hi!');");
I hope this help you!
in this code Android app opens a web page with WebView and extracts a text from HTML which is between tags "body" and "/body".
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.TextView;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
public class MainAc extends Activity {
/** Called when the activity is first created. */
#SuppressLint({ "JavascriptInterface", "SetJavaScriptEnabled" })
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
WebView webView = (WebView) findViewById(R.id.web);
TextView text2 = (TextView) findViewById(R.id.text);
Button infoButton = (Button) findViewById(R.id.b1);
infoButton.setOnClickListener(new OnClickListener(){
public void onClick(View view){
// here is your button click logic, for example running another activity (page)
startActivity(new Intent(MainAc.this, JavaInterface.class));
}
});
class Javasc {
private TextView t2;
public Javasc (TextView i)
{
t2 = i;
}
#SuppressWarnings("unused")
public void processContent(String ii)
{
final String content = ii;
t2.post(new Runnable()
{
public void run()
{
t2.setText(content);
}
});
}
}
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new Javasc(text2), "INTERFACE");
webView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url)
{
view.loadUrl("javascript:window.INTERFACE.processContent(document.getElementsByTagName('body')[0].innerText);");
}
});
webView.loadUrl("http://www.nytimes.com/2014/08/03/sports/basketball/pacers-paul-george-has-surgery-after-badly-injuring-leg.html?ref=sports");
}
}
Is it possible to use JavaScript functions for extracted text in android's TextView ?
for example this JavaScript function (or it could be any other JS function where need to work with text)
function myFunction() {
var text = document.body.innerText;
var titles =text.match(/^\n(.+?)\n\n/mg);
for (var i = 0; i < titles.length; i++) {
document.write(titles[i] + "<br />" + "<br />");
}
}
Thanks for answers :)
According to this article, the Dalvik VM supports Java's scripting features (javax.script). One of the premier languages supported by the javax.script stuff is, unsurprisingly, JavaScript.
So in theory, you can use the javax.script stuff to execute JavaScript code and get back results. I think (also based on that article), that you have to include the relevant jar(s) (javax.script isn't in the Android SDK). Fortunately, though, javax.script is largely a set of interfaces, which are implemented by jars for specific scripting languages.
Some resources about using javax.script to run script code:
The Java Scripting API (Oracle)
Java Scripting Programmer's Guide (Oracle)
You can use Rhino to achieve this. See specifically Context.jsToJava.
https://vec.io/posts/embed-javascript-in-android-java-code-with-rhino
You want to do a regex on a text and resorting to javascript for it, which is unnecessary when you have a better faster API in java with no overhead.
The equivalent to your example is:
http://developer.android.com/reference/java/util/regex/Pattern.html
Pattern p = Pattern.compile("/^\n(.+?)\n\n/mg");
Matcher m = p.matcher(myTextView.getText());
while (m.find()) {
String titles = m.group(1);
Log.V("TAG", titles);
}
There are plenty of other text analysis solutions included in the core framework or external libraries that have been proved for both mobile and server. You just have to look it up and not resort to a worse API you're comfortable with.
I have seen lots of problems with using ProGuard on applications that include webviews with JavascriptInterfaces, but none of the solutions seem to work for me so I must be missing something.
I have this activity
public class MapviewActivity extends Activity {
private WebView webView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.webview_map);
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setTitle("The Map");
webView = (WebView) findViewById(R.id.map_webview);
webView.clearCache(true);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setSupportZoom(true);
webView.getSettings().setLoadWithOverviewMode(true);
webView.getSettings().setUseWideViewPort(true);
webView.setInitialScale(1);
webView.getSettings().setUseWideViewPort(true);
webView.loadUrl("file:///android_asset/map.html");
webView.addJavascriptInterface(new MapviewJSInterface(this), "Android");
}
public class MapviewJSInterface {
public Context mContext;
public MapviewJSInterface(MapviewActivity mapviewActivity) {
this.mContext = mapviewActivity;
}
#JavascriptInterface
public void showResidents(String locCode) {
Intent intent = new Intent(mContext, DialogResidentsList.class);
intent.putExtra("colorString", locCode);
mContext.startActivity(intent);
}
}
}
my proguard-project.txt includes this
-keep class tv.cmc2.zaratancodex.controller.MapviewActivity$MapviewJSInterface
-keepclassmembers class tv.cmc2.zaratancodex.controller.MapviewActivity$MapviewJSInterface {
public void showResidents(java.lang.String);
}
but I keep getting this error when I try to export an apk
Warning: tv.cmc2.zaratancodex.view.MapviewActivity$MapviewJSInterface: can't find referenced class android.webkit.JavascriptInterface
there must be something else that I am missing because those are the rules that seem to be working for everyone else.
You should build against a version of the Android runtime that contains the class android.webkit.JavascriptInterface. That would be android-17 or higher. ProGuard then won't complain that it can't find it.
If your code uses the annotation to mark Javascript interface classes (as required in recent versions of the Android SDK), you can also use it in your ProGuard configuration:
-keepclassmembers class * {
#android.webkit.JavascriptInterface <methods>;
}
You then no longer need to exhaustively list the interface classes and methods.