349989153 / 349989153.github.io Goto Github PK
View Code? Open in Web Editor NEWMy personal blog.
My personal blog.
最外层的盒子设置为弹性盒子,左边和右边的写成固定宽度,中间的input输入框设置为flex:1,希望input的宽度是所剩下的长度,结果是它比所剩下的长度要大,验证码这三个字就显示成两行了
原因:input兼容弹性盒子有问题,它会有一个自己默认的最小长度,所以会导致验证码显示成两行
解决办法:我们可以给input输入框加一个div父元素,然后这个div设置flex:1,input设置width:100%;即可解决问题
package com.reactnativecommunity.webview;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.Manifest;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.webkit.ConsoleMessage;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.GeolocationPermissions;
import android.webkit.JavascriptInterface;
import android.webkit.PermissionRequest;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import com.facebook.react.views.scroll.ScrollEvent;
import com.facebook.react.views.scroll.ScrollEventType;
import com.facebook.react.views.scroll.OnScrollDispatchHelper;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.reactnativecommunity.webview.events.TopLoadingErrorEvent;
import com.reactnativecommunity.webview.events.TopHttpErrorEvent;
import com.reactnativecommunity.webview.events.TopLoadingFinishEvent;
import com.reactnativecommunity.webview.events.TopLoadingProgressEvent;
import com.reactnativecommunity.webview.events.TopLoadingStartEvent;
import com.reactnativecommunity.webview.events.TopMessageEvent;
import com.reactnativecommunity.webview.events.TopShouldStartLoadWithRequestEvent;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Manages instances of {@link WebView}
* <p>
* Can accept following commands:
* - GO_BACK
* - GO_FORWARD
* - RELOAD
* - LOAD_URL
* <p>
* {@link WebView} instances could emit following direct events:
* - topLoadingFinish
* - topLoadingStart
* - topLoadingStart
* - topLoadingProgress
* - topShouldStartLoadWithRequest
* <p>
* Each event will carry the following properties:
* - target - view's react tag
* - url - url set for the webview
* - loading - whether webview is in a loading state
* - title - title of the current page
* - canGoBack - boolean, whether there is anything on a history stack to go back
* - canGoForward - boolean, whether it is possible to request GO_FORWARD command
*/
@ReactModule(name = RNCWebViewManager.REACT_CLASS)
public class RNCWebViewManager extends SimpleViewManager<WebView> {
public static String activeUrl = null;
public static final int COMMAND_GO_BACK = 1;
public static final int COMMAND_GO_FORWARD = 2;
public static final int COMMAND_RELOAD = 3;
public static final int COMMAND_STOP_LOADING = 4;
public static final int COMMAND_POST_MESSAGE = 5;
public static final int COMMAND_INJECT_JAVASCRIPT = 6;
public static final int COMMAND_LOAD_URL = 7;
public static final int COMMAND_FOCUS = 8;
// android commands
public static final int COMMAND_CLEAR_FORM_DATA = 1000;
public static final int COMMAND_CLEAR_CACHE = 1001;
public static final int COMMAND_CLEAR_HISTORY = 1002;
protected static final String REACT_CLASS = "RNCWebView";
protected static final String HTML_ENCODING = "UTF-8";
protected static final String HTML_MIME_TYPE = "text/html";
protected static final String JAVASCRIPT_INTERFACE = "ReactNativeWebView";
protected static final String HTTP_METHOD_POST = "POST";
// Use `webView.loadUrl("about:blank")` to reliably reset the view
// state and release page resources (including any running JavaScript).
protected static final String BLANK_URL = "about:blank";
protected WebViewConfig mWebViewConfig;
protected RNCWebChromeClient mWebChromeClient = null;
protected boolean mAllowsFullscreenVideo = false;
protected @Nullable String mUserAgent = null;
protected @Nullable String mUserAgentWithApplicationName = null;
public RNCWebViewManager() {
mWebViewConfig = new WebViewConfig() {
public void configWebView(WebView webView) {
}
};
}
public RNCWebViewManager(WebViewConfig webViewConfig) {
mWebViewConfig = webViewConfig;
}
protected static void dispatchEvent(WebView webView, Event event) {
ReactContext reactContext = (ReactContext) webView.getContext();
EventDispatcher eventDispatcher =
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
eventDispatcher.dispatchEvent(event);
}
@Override
public String getName() {
return REACT_CLASS;
}
protected RNCWebView createRNCWebViewInstance(ThemedReactContext reactContext) {
return new RNCWebView(reactContext);
}
@Override
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
protected WebView createViewInstance(ThemedReactContext reactContext) {
RNCWebView webView = createRNCWebViewInstance(reactContext);
// todo test
// webView.setWebChromeClient(new WebChromeClient());
// webView.setWebViewClient(new RNCWebViewClient());
setupWebChromeClient(reactContext, webView);
reactContext.addLifecycleEventListener(webView);
mWebViewConfig.configWebView(webView);
WebSettings settings = webView.getSettings();
settings.setBuiltInZoomControls(true);
settings.setDisplayZoomControls(false);
settings.setDomStorageEnabled(true);
settings.setAppCacheEnabled(true);
settings.setDatabaseEnabled(true);
// todo test
// settings.setPluginState(WebSettings.PluginState.ON);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
settings.setPluginState(WebSettings.PluginState.ON);
settings.setMediaPlaybackRequiresUserGesture(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
settings.setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(webView, false);
}
setMixedContentMode(webView, "never");
// Fixes broken full-screen modals/galleries due to body height being 0.
webView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
webView.setDownloadListener(new DownloadListener() {
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
RNCWebViewModule module = getModule(reactContext);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);
String downloadMessage = "Downloading " + fileName;
//Attempt to add cookie, if it exists
URL urlObj = null;
try {
urlObj = new URL(url);
String baseUrl = urlObj.getProtocol() + "://" + urlObj.getHost();
String cookie = CookieManager.getInstance().getCookie(baseUrl);
request.addRequestHeader("Cookie", cookie);
} catch (MalformedURLException e) {
System.out.println("Error getting cookie for DownloadManager: " + e.toString());
e.printStackTrace();
}
//Finish setting up request
request.addRequestHeader("User-Agent", userAgent);
request.setTitle(fileName);
request.setDescription(downloadMessage);
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
module.setDownloadRequest(request);
if (module.grantFileDownloaderPermissions()) {
module.downloadFile();
}
}
});
return webView;
}
@ReactProp(name = "javaScriptEnabled")
public void setJavaScriptEnabled(WebView view, boolean enabled) {
view.getSettings().setJavaScriptEnabled(enabled);
}
@ReactProp(name = "showsHorizontalScrollIndicator")
public void setShowsHorizontalScrollIndicator(WebView view, boolean enabled) {
view.setHorizontalScrollBarEnabled(enabled);
}
@ReactProp(name = "showsVerticalScrollIndicator")
public void setShowsVerticalScrollIndicator(WebView view, boolean enabled) {
view.setVerticalScrollBarEnabled(enabled);
}
@ReactProp(name = "cacheEnabled")
public void setCacheEnabled(WebView view, boolean enabled) {
if (enabled) {
Context ctx = view.getContext();
if (ctx != null) {
view.getSettings().setAppCachePath(ctx.getCacheDir().getAbsolutePath());
view.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
view.getSettings().setAppCacheEnabled(true);
}
} else {
view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
view.getSettings().setAppCacheEnabled(false);
}
}
@ReactProp(name = "cacheMode")
public void setCacheMode(WebView view, String cacheModeString) {
Integer cacheMode;
switch (cacheModeString) {
case "LOAD_CACHE_ONLY":
cacheMode = WebSettings.LOAD_CACHE_ONLY;
break;
case "LOAD_CACHE_ELSE_NETWORK":
cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK;
break;
case "LOAD_NO_CACHE":
cacheMode = WebSettings.LOAD_NO_CACHE;
break;
case "LOAD_DEFAULT":
default:
cacheMode = WebSettings.LOAD_DEFAULT;
break;
}
view.getSettings().setCacheMode(cacheMode);
}
@ReactProp(name = "androidHardwareAccelerationDisabled")
public void setHardwareAccelerationDisabled(WebView view, boolean disabled) {
if (disabled) {
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
}
@ReactProp(name = "overScrollMode")
public void setOverScrollMode(WebView view, String overScrollModeString) {
Integer overScrollMode;
switch (overScrollModeString) {
case "never":
overScrollMode = View.OVER_SCROLL_NEVER;
break;
case "content":
overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS;
break;
case "always":
default:
overScrollMode = View.OVER_SCROLL_ALWAYS;
break;
}
view.setOverScrollMode(overScrollMode);
}
@ReactProp(name = "thirdPartyCookiesEnabled")
public void setThirdPartyCookiesEnabled(WebView view, boolean enabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setAcceptThirdPartyCookies(view, enabled);
}
}
@ReactProp(name = "textZoom")
public void setTextZoom(WebView view, int value) {
view.getSettings().setTextZoom(value);
}
@ReactProp(name = "scalesPageToFit")
public void setScalesPageToFit(WebView view, boolean enabled) {
view.getSettings().setLoadWithOverviewMode(enabled);
view.getSettings().setUseWideViewPort(enabled);
}
@ReactProp(name = "domStorageEnabled")
public void setDomStorageEnabled(WebView view, boolean enabled) {
view.getSettings().setDomStorageEnabled(enabled);
}
@ReactProp(name = "userAgent")
public void setUserAgent(WebView view, @Nullable String userAgent) {
if (userAgent != null) {
mUserAgent = userAgent;
} else {
mUserAgent = null;
}
this.setUserAgentString(view);
}
@ReactProp(name = "applicationNameForUserAgent")
public void setApplicationNameForUserAgent(WebView view, @Nullable String applicationName) {
if(applicationName != null) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
String defaultUserAgent = WebSettings.getDefaultUserAgent(view.getContext());
mUserAgentWithApplicationName = defaultUserAgent + " " + applicationName;
}
} else {
mUserAgentWithApplicationName = null;
}
this.setUserAgentString(view);
}
protected void setUserAgentString(WebView view) {
if(mUserAgent != null) {
view.getSettings().setUserAgentString(mUserAgent);
} else if(mUserAgentWithApplicationName != null) {
view.getSettings().setUserAgentString(mUserAgentWithApplicationName);
} else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
// handle unsets of `userAgent` prop as long as device is >= API 17
view.getSettings().setUserAgentString(WebSettings.getDefaultUserAgent(view.getContext()));
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@ReactProp(name = "mediaPlaybackRequiresUserAction")
public void setMediaPlaybackRequiresUserAction(WebView view, boolean requires) {
view.getSettings().setMediaPlaybackRequiresUserGesture(requires);
}
@ReactProp(name = "allowFileAccessFromFileURLs")
public void setAllowFileAccessFromFileURLs(WebView view, boolean allow) {
view.getSettings().setAllowFileAccessFromFileURLs(allow);
}
@ReactProp(name = "allowUniversalAccessFromFileURLs")
public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
}
@ReactProp(name = "saveFormDataDisabled")
public void setSaveFormDataDisabled(WebView view, boolean disable) {
view.getSettings().setSaveFormData(!disable);
}
@ReactProp(name = "injectedJavaScript")
public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
((RNCWebView) view).setInjectedJavaScript(injectedJavaScript);
}
@ReactProp(name = "messagingEnabled")
public void setMessagingEnabled(WebView view, boolean enabled) {
((RNCWebView) view).setMessagingEnabled(enabled);
}
@ReactProp(name = "incognito")
public void setIncognito(WebView view, boolean enabled) {
// Remove all previous cookies
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().removeAllCookies(null);
} else {
CookieManager.getInstance().removeAllCookie();
}
// Disable caching
view.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
view.getSettings().setAppCacheEnabled(!enabled);
view.clearHistory();
view.clearCache(enabled);
// No form data or autofill enabled
view.clearFormData();
view.getSettings().setSavePassword(!enabled);
view.getSettings().setSaveFormData(!enabled);
}
@ReactProp(name = "source")
public void setSource(WebView view, @Nullable ReadableMap source) {
// todo test
// view.setWebViewClient(new RNCWebViewClient());
if (source != null) {
if (source.hasKey("html")) {
String html = source.getString("html");
String baseUrl = source.hasKey("baseUrl") ? source.getString("baseUrl") : "";
view.loadDataWithBaseURL(baseUrl, html, HTML_MIME_TYPE, HTML_ENCODING, null);
return;
}
if (source.hasKey("uri")) {
String url = source.getString("uri");
String previousUrl = view.getUrl();
if (previousUrl != null && previousUrl.equals(url)) {
return;
}
if (source.hasKey("method")) {
String method = source.getString("method");
if (method.equalsIgnoreCase(HTTP_METHOD_POST)) {
byte[] postData = null;
if (source.hasKey("body")) {
String body = source.getString("body");
try {
postData = body.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
postData = body.getBytes();
}
}
if (postData == null) {
postData = new byte[0];
}
view.postUrl(url, postData);
return;
}
}
HashMap<String, String> headerMap = new HashMap<>();
if (source.hasKey("headers")) {
ReadableMap headers = source.getMap("headers");
ReadableMapKeySetIterator iter = headers.keySetIterator();
while (iter.hasNextKey()) {
String key = iter.nextKey();
if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
if (view.getSettings() != null) {
view.getSettings().setUserAgentString(headers.getString(key));
}
} else {
headerMap.put(key, headers.getString(key));
}
}
}
view.loadUrl(url, headerMap);
return;
}
}
view.loadUrl(BLANK_URL);
}
@ReactProp(name = "onContentSizeChange")
public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
((RNCWebView) view).setSendContentSizeChangeEvents(sendContentSizeChangeEvents);
}
@ReactProp(name = "mixedContentMode")
public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (mixedContentMode == null || "never".equals(mixedContentMode)) {
view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
} else if ("always".equals(mixedContentMode)) {
view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
} else if ("compatibility".equals(mixedContentMode)) {
view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
}
}
}
@ReactProp(name = "urlPrefixesForDefaultIntent")
public void setUrlPrefixesForDefaultIntent(
WebView view,
@Nullable ReadableArray urlPrefixesForDefaultIntent) {
RNCWebViewClient client = ((RNCWebView) view).getRNCWebViewClient();
if (client != null && urlPrefixesForDefaultIntent != null) {
client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
}
}
@ReactProp(name = "allowsFullscreenVideo")
public void setAllowsFullscreenVideo(
WebView view,
@Nullable Boolean allowsFullscreenVideo) {
mAllowsFullscreenVideo = allowsFullscreenVideo != null && allowsFullscreenVideo;
setupWebChromeClient((ReactContext)view.getContext(), view);
}
@ReactProp(name = "allowFileAccess")
public void setAllowFileAccess(
WebView view,
@Nullable Boolean allowFileAccess) {
view.getSettings().setAllowFileAccess(allowFileAccess != null && allowFileAccess);
}
@ReactProp(name = "geolocationEnabled")
public void setGeolocationEnabled(
WebView view,
@Nullable Boolean isGeolocationEnabled) {
view.getSettings().setGeolocationEnabled(isGeolocationEnabled != null && isGeolocationEnabled);
}
@ReactProp(name = "onScroll")
public void setOnScroll(WebView view, boolean hasScrollEvent) {
((RNCWebView) view).setHasScrollEvent(hasScrollEvent);
}
@Override
protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
// Do not register default touch emitter and let WebView implementation handle touches
view.setWebViewClient(new RNCWebViewClient());
}
@Override
public Map getExportedCustomDirectEventTypeConstants() {
Map export = super.getExportedCustomDirectEventTypeConstants();
if (export == null) {
export = MapBuilder.newHashMap();
}
export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress"));
export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest"));
export.put(ScrollEventType.getJSEventName(ScrollEventType.SCROLL), MapBuilder.of("registrationName", "onScroll"));
export.put(TopHttpErrorEvent.EVENT_NAME, MapBuilder.of("registrationName", "onHttpError"));
return export;
}
@Override
public @Nullable
Map<String, Integer> getCommandsMap() {
return MapBuilder.<String, Integer>builder()
.put("goBack", COMMAND_GO_BACK)
.put("goForward", COMMAND_GO_FORWARD)
.put("reload", COMMAND_RELOAD)
.put("stopLoading", COMMAND_STOP_LOADING)
.put("postMessage", COMMAND_POST_MESSAGE)
.put("injectJavaScript", COMMAND_INJECT_JAVASCRIPT)
.put("loadUrl", COMMAND_LOAD_URL)
.put("requestFocus", COMMAND_FOCUS)
.put("clearFormData", COMMAND_CLEAR_FORM_DATA)
.put("clearCache", COMMAND_CLEAR_CACHE)
.put("clearHistory", COMMAND_CLEAR_HISTORY)
.build();
}
@Override
public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {
switch (commandId) {
case COMMAND_GO_BACK:
root.goBack();
break;
case COMMAND_GO_FORWARD:
root.goForward();
break;
case COMMAND_RELOAD:
root.reload();
break;
case COMMAND_STOP_LOADING:
root.stopLoading();
break;
case COMMAND_POST_MESSAGE:
try {
RNCWebView reactWebView = (RNCWebView) root;
JSONObject eventInitDict = new JSONObject();
eventInitDict.put("data", args.getString(0));
reactWebView.evaluateJavascriptWithFallback("(function () {" +
"var event;" +
"var data = " + eventInitDict.toString() + ";" +
"try {" +
"event = new MessageEvent('message', data);" +
"} catch (e) {" +
"event = document.createEvent('MessageEvent');" +
"event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
"}" +
"document.dispatchEvent(event);" +
"})();");
} catch (JSONException e) {
throw new RuntimeException(e);
}
break;
case COMMAND_INJECT_JAVASCRIPT:
RNCWebView reactWebView = (RNCWebView) root;
reactWebView.evaluateJavascriptWithFallback(args.getString(0));
break;
case COMMAND_LOAD_URL:
if (args == null) {
throw new RuntimeException("Arguments for loading an url are null!");
}
root.loadUrl(args.getString(0));
break;
case COMMAND_FOCUS:
root.requestFocus();
break;
case COMMAND_CLEAR_FORM_DATA:
root.clearFormData();
break;
case COMMAND_CLEAR_CACHE:
boolean includeDiskFiles = args != null && args.getBoolean(0);
root.clearCache(includeDiskFiles);
break;
case COMMAND_CLEAR_HISTORY:
root.clearHistory();
break;
}
}
@Override
public void onDropViewInstance(WebView webView) {
super.onDropViewInstance(webView);
((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((RNCWebView) webView);
((RNCWebView) webView).cleanupCallbacksAndDestroy();
}
public static RNCWebViewModule getModule(ReactContext reactContext) {
return reactContext.getNativeModule(RNCWebViewModule.class);
}
protected void setupWebChromeClient(ReactContext reactContext, WebView webView) {
if (mAllowsFullscreenVideo) {
int initialRequestedOrientation = reactContext.getCurrentActivity().getRequestedOrientation();
mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
@Override
public Bitmap getDefaultVideoPoster() {
return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
if (mVideoView != null) {
callback.onCustomViewHidden();
return;
}
mVideoView = view;
mCustomViewCallback = callback;
mReactContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
mReactContext.getCurrentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
mVideoView.setBackgroundColor(Color.BLACK);
getRootView().addView(mVideoView, FULLSCREEN_LAYOUT_PARAMS);
mWebView.setVisibility(View.GONE);
mReactContext.addLifecycleEventListener(this);
}
@Override
public void onHideCustomView() {
if (mVideoView == null) {
return;
}
mVideoView.setVisibility(View.GONE);
getRootView().removeView(mVideoView);
mCustomViewCallback.onCustomViewHidden();
mVideoView = null;
mCustomViewCallback = null;
mWebView.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mReactContext.getCurrentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
mReactContext.getCurrentActivity().setRequestedOrientation(initialRequestedOrientation);
mReactContext.removeLifecycleEventListener(this);
}
};
webView.setWebChromeClient(mWebChromeClient);
} else {
if (mWebChromeClient != null) {
mWebChromeClient.onHideCustomView();
}
mWebChromeClient = new RNCWebChromeClient(reactContext, webView) {
@Override
public Bitmap getDefaultVideoPoster() {
return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
}
};
webView.setWebChromeClient(mWebChromeClient);
}
}
protected static class RNCWebViewClient extends WebViewClient {
protected boolean mLastLoadFailed = false;
protected @Nullable
ReadableArray mUrlPrefixesForDefaultIntent;
@Override
public void onPageFinished(WebView webView, String url) {
super.onPageFinished(webView, url);
if (!mLastLoadFailed) {
RNCWebView reactWebView = (RNCWebView) webView;
reactWebView.callInjectedJavaScript();
emitFinishEvent(webView, url);
}
}
@Override
public void onPageStarted(WebView webView, String url, Bitmap favicon) {
super.onPageStarted(webView, url, favicon);
mLastLoadFailed = false;
dispatchEvent(
webView,
new TopLoadingStartEvent(
webView.getId(),
createWebViewEvent(webView, url)));
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
activeUrl = url;
dispatchEvent(
view,
new TopShouldStartLoadWithRequestEvent(
view.getId(),
createWebViewEvent(view, url)));
return true;
}
@TargetApi(Build.VERSION_CODES.N)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
final String url = request.getUrl().toString();
return this.shouldOverrideUrlLoading(view, url);
}
@Override
public void onReceivedError(
WebView webView,
int errorCode,
String description,
String failingUrl) {
super.onReceivedError(webView, errorCode, description, failingUrl);
mLastLoadFailed = true;
// In case of an error JS side expect to get a finish event first, and then get an error event
// Android WebView does it in the opposite way, so we need to simulate that behavior
emitFinishEvent(webView, failingUrl);
WritableMap eventData = createWebViewEvent(webView, failingUrl);
eventData.putDouble("code", errorCode);
eventData.putString("description", description);
dispatchEvent(
webView,
new TopLoadingErrorEvent(webView.getId(), eventData));
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onReceivedHttpError(
WebView webView,
WebResourceRequest request,
WebResourceResponse errorResponse) {
super.onReceivedHttpError(webView, request, errorResponse);
if (request.isForMainFrame()) {
WritableMap eventData = createWebViewEvent(webView, request.getUrl().toString());
eventData.putInt("statusCode", errorResponse.getStatusCode());
eventData.putString("description", errorResponse.getReasonPhrase());
dispatchEvent(
webView,
new TopHttpErrorEvent(webView.getId(), eventData));
}
}
protected void emitFinishEvent(WebView webView, String url) {
dispatchEvent(
webView,
new TopLoadingFinishEvent(
webView.getId(),
createWebViewEvent(webView, url)));
}
protected WritableMap createWebViewEvent(WebView webView, String url) {
WritableMap event = Arguments.createMap();
event.putDouble("target", webView.getId());
// Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
// like onPageFinished
event.putString("url", url);
event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
event.putString("title", webView.getTitle());
event.putBoolean("canGoBack", webView.canGoBack());
event.putBoolean("canGoForward", webView.canGoForward());
return event;
}
public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
mUrlPrefixesForDefaultIntent = specialUrls;
}
}
protected static class RNCWebChromeClient extends WebChromeClient implements LifecycleEventListener {
protected static final FrameLayout.LayoutParams FULLSCREEN_LAYOUT_PARAMS = new FrameLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER);
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
protected static final int FULLSCREEN_SYSTEM_UI_VISIBILITY = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
protected ReactContext mReactContext;
protected View mWebView;
protected View mVideoView;
protected WebChromeClient.CustomViewCallback mCustomViewCallback;
public RNCWebChromeClient(ReactContext reactContext, WebView webView) {
this.mReactContext = reactContext;
this.mWebView = webView;
}
@Override
public boolean onConsoleMessage(ConsoleMessage message) {
if (ReactBuildConfig.DEBUG) {
return super.onConsoleMessage(message);
}
// Ignore console logs in non debug builds.
return true;
}
// Fix WebRTC permission request error.
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onPermissionRequest(final PermissionRequest request) {
String[] requestedResources = request.getResources();
ArrayList<String> permissions = new ArrayList<>();
ArrayList<String> grantedPermissions = new ArrayList<String>();
for (int i = 0; i < requestedResources.length; i++) {
if (requestedResources[i].equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) {
permissions.add(Manifest.permission.RECORD_AUDIO);
} else if (requestedResources[i].equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
permissions.add(Manifest.permission.CAMERA);
}
// TODO: RESOURCE_MIDI_SYSEX, RESOURCE_PROTECTED_MEDIA_ID.
}
for (int i = 0; i < permissions.size(); i++) {
if (ContextCompat.checkSelfPermission(mReactContext, permissions.get(i)) != PackageManager.PERMISSION_GRANTED) {
continue;
}
if (permissions.get(i).equals(Manifest.permission.RECORD_AUDIO)) {
grantedPermissions.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
} else if (permissions.get(i).equals(Manifest.permission.CAMERA)) {
grantedPermissions.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
}
}
if (grantedPermissions.isEmpty()) {
request.deny();
} else {
String[] grantedPermissionsArray = new String[grantedPermissions.size()];
grantedPermissionsArray = grantedPermissions.toArray(grantedPermissionsArray);
request.grant(grantedPermissionsArray);
}
}
@Override
public void onProgressChanged(WebView webView, int newProgress) {
super.onProgressChanged(webView, newProgress);
final String url = webView.getUrl();
if (
url != null
&& activeUrl != null
&& !url.equals(activeUrl)
) {
return;
}
WritableMap event = Arguments.createMap();
event.putDouble("target", webView.getId());
event.putString("title", webView.getTitle());
event.putString("url", url);
event.putBoolean("canGoBack", webView.canGoBack());
event.putBoolean("canGoForward", webView.canGoForward());
event.putDouble("progress", (float) newProgress / 100);
dispatchEvent(
webView,
new TopLoadingProgressEvent(
webView.getId(),
event));
}
@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
callback.invoke(origin, true, false);
}
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
}
protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
getModule(mReactContext).startPhotoPickerIntent(filePathCallback, "");
}
protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
getModule(mReactContext).startPhotoPickerIntent(filePathCallback, acceptType);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
String[] acceptTypes = fileChooserParams.getAcceptTypes();
boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
Intent intent = fileChooserParams.createIntent();
return getModule(mReactContext).startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple);
}
@Override
public void onHostResume() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mVideoView != null && mVideoView.getSystemUiVisibility() != FULLSCREEN_SYSTEM_UI_VISIBILITY) {
mVideoView.setSystemUiVisibility(FULLSCREEN_SYSTEM_UI_VISIBILITY);
}
}
@Override
public void onHostPause() { }
@Override
public void onHostDestroy() { }
protected ViewGroup getRootView() {
return (ViewGroup) mReactContext.getCurrentActivity().findViewById(android.R.id.content);
}
}
/**
* Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
* to call {@link WebView#destroy} on activity destroy event and also to clear the client
*/
protected static class RNCWebView extends WebView implements LifecycleEventListener {
protected @Nullable
String injectedJS;
protected boolean messagingEnabled = false;
protected @Nullable
RNCWebViewClient mRNCWebViewClient;
protected boolean sendContentSizeChangeEvents = false;
private OnScrollDispatchHelper mOnScrollDispatchHelper;
protected boolean hasScrollEvent = false;
/**
* WebView must be created with an context of the current activity
* <p>
* Activity Context is required for creation of dialogs internally by WebView
* Reactive Native needed for access to ReactNative internal system functionality
*/
public RNCWebView(ThemedReactContext reactContext) {
super(reactContext);
}
public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
this.sendContentSizeChangeEvents = sendContentSizeChangeEvents;
}
public void setHasScrollEvent(boolean hasScrollEvent) {
this.hasScrollEvent = hasScrollEvent;
}
@Override
public void onHostResume() {
// do nothing
}
@Override
public void onHostPause() {
// do nothing
}
@Override
public void onHostDestroy() {
cleanupCallbacksAndDestroy();
}
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
if (sendContentSizeChangeEvents) {
dispatchEvent(
this,
new ContentSizeChangeEvent(
this.getId(),
w,
h
)
);
}
}
@Override
public void setWebViewClient(WebViewClient client) {
super.setWebViewClient(client);
if (client instanceof RNCWebViewClient) {
mRNCWebViewClient = (RNCWebViewClient) client;
}
}
public @Nullable
RNCWebViewClient getRNCWebViewClient() {
return mRNCWebViewClient;
}
public void setInjectedJavaScript(@Nullable String js) {
injectedJS = js;
}
protected RNCWebViewBridge createRNCWebViewBridge(RNCWebView webView) {
return new RNCWebViewBridge(webView);
}
@SuppressLint("AddJavascriptInterface")
public void setMessagingEnabled(boolean enabled) {
if (messagingEnabled == enabled) {
return;
}
messagingEnabled = enabled;
if (enabled) {
addJavascriptInterface(createRNCWebViewBridge(this), JAVASCRIPT_INTERFACE);
} else {
removeJavascriptInterface(JAVASCRIPT_INTERFACE);
}
}
protected void evaluateJavascriptWithFallback(String script) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
evaluateJavascript(script, null);
return;
}
try {
loadUrl("javascript:" + URLEncoder.encode(script, "UTF-8"));
} catch (UnsupportedEncodingException e) {
// UTF-8 should always be supported
throw new RuntimeException(e);
}
}
public void callInjectedJavaScript() {
if (getSettings().getJavaScriptEnabled() &&
injectedJS != null &&
!TextUtils.isEmpty(injectedJS)) {
evaluateJavascriptWithFallback("(function() {\n" + injectedJS + ";\n})();");
}
}
public void onMessage(String message) {
if (mRNCWebViewClient != null) {
WebView webView = this;
webView.post(new Runnable() {
@Override
public void run() {
if (mRNCWebViewClient == null) {
return;
}
WritableMap data = mRNCWebViewClient.createWebViewEvent(webView, webView.getUrl());
data.putString("data", message);
dispatchEvent(webView, new TopMessageEvent(webView.getId(), data));
}
});
} else {
WritableMap eventData = Arguments.createMap();
eventData.putString("data", message);
dispatchEvent(this, new TopMessageEvent(this.getId(), eventData));
}
}
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
super.onScrollChanged(x, y, oldX, oldY);
if (!hasScrollEvent) {
return;
}
if (mOnScrollDispatchHelper == null) {
mOnScrollDispatchHelper = new OnScrollDispatchHelper();
}
if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
ScrollEvent event = ScrollEvent.obtain(
this.getId(),
ScrollEventType.SCROLL,
x,
y,
mOnScrollDispatchHelper.getXFlingVelocity(),
mOnScrollDispatchHelper.getYFlingVelocity(),
this.computeHorizontalScrollRange(),
this.computeVerticalScrollRange(),
this.getWidth(),
this.getHeight());
dispatchEvent(this, event);
}
}
protected void cleanupCallbacksAndDestroy() {
setWebViewClient(null);
destroy();
}
protected class RNCWebViewBridge {
RNCWebView mContext;
RNCWebViewBridge(RNCWebView c) {
mContext = c;
}
/**
* This method is called whenever JavaScript running within the web view calls:
* - window[JAVASCRIPT_INTERFACE].postMessage
*/
@JavascriptInterface
public void postMessage(String message) {
mContext.onMessage(message);
}
}
}
}
package com.brentvatne.exoplayer;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.CaptioningManager;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import com.brentvatne.react.R;
import com.brentvatne.receiver.AudioBecomingNoisyReceiver;
import com.brentvatne.receiver.BecomingNoisyListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.SingleSampleMediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Util;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
@SuppressLint("ViewConstructor")
class ReactExoplayerView extends FrameLayout implements
LifecycleEventListener,
Player.EventListener,
BandwidthMeter.EventListener,
BecomingNoisyListener,
AudioManager.OnAudioFocusChangeListener,
MetadataOutput {
private static final String TAG = "ReactExoplayerView";
private static final CookieManager DEFAULT_COOKIE_MANAGER;
private static final int SHOW_PROGRESS = 1;
static {
DEFAULT_COOKIE_MANAGER = new CookieManager();
DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
}
private final VideoEventEmitter eventEmitter;
private final ReactExoplayerConfig config;
private final DefaultBandwidthMeter bandwidthMeter;
private PlayerControlView playerControlView;
private View playPauseControlContainer;
private Player.EventListener eventListener;
private PlayerView exoPlayerView;
private int initialOrientation;
private DataSource.Factory mediaDataSourceFactory;
private SimpleExoPlayer player;
private DefaultTrackSelector trackSelector;
private boolean playerNeedsSource;
private int resumeWindow;
private long resumePosition;
private boolean loadVideoStarted;
private boolean isFullscreen;
private boolean isInBackground;
private boolean isPaused;
private boolean isBuffering;
private boolean muted = false;
private float rate = 1f;
private float audioVolume = 1f;
private int minLoadRetryCount = 3;
private int maxBitRate = 0;
private long seekTime = C.TIME_UNSET;
private int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
// Props from React
private Uri srcUri;
private String extension;
private boolean repeat;
private String audioTrackType;
private Dynamic audioTrackValue;
private String videoTrackType;
private Dynamic videoTrackValue;
private String textTrackType;
private Dynamic textTrackValue;
private ReadableArray textTracks;
private boolean disableFocus;
private float mProgressUpdateInterval = 250.0f;
private boolean playInBackground = false;
private Map<String, String> requestHeaders;
private boolean mReportBandwidth = false;
private boolean controls;
// \ End props
// React
private final ThemedReactContext themedReactContext;
private final AudioManager audioManager;
private final AudioBecomingNoisyReceiver audioBecomingNoisyReceiver;
private final Handler progressHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PROGRESS:
if (player != null
&& player.getPlaybackState() == Player.STATE_READY
&& player.getPlayWhenReady()
) {
long pos = player.getCurrentPosition();
long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100;
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration());
msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
}
break;
}
}
};
public ReactExoplayerView(ThemedReactContext context, ReactExoplayerConfig config) {
super(context);
this.themedReactContext = context;
this.initialOrientation = getResources().getConfiguration().orientation;
this.eventEmitter = new VideoEventEmitter(context);
this.config = config;
this.bandwidthMeter = config.getBandwidthMeter();
createViews();
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
themedReactContext.addLifecycleEventListener(this);
audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext);
initializePlayer();
}
@Override
public void setId(int id) {
super.setId(id);
eventEmitter.setViewId(id);
}
private void createViews() {
clearResumePosition();
mediaDataSourceFactory = buildDataSourceFactory(true);
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
}
LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
exoPlayerView = new PlayerView(getContext());
exoPlayerView.setLayoutParams(layoutParams);
addView(exoPlayerView, 0, layoutParams);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
initializePlayer();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
/* We want to be able to continue playing audio when switching tabs.
* Leave this here in case it causes issues.
*/
// stopPlayback();
}
// LifecycleEventListener implementation
@Override
public void onHostResume() {
if (!playInBackground || !isInBackground) {
setPlayWhenReady(!isPaused);
}
isInBackground = false;
}
@Override
public void onHostPause() {
isInBackground = true;
if (playInBackground) {
return;
}
setPlayWhenReady(false);
}
@Override
public void onHostDestroy() {
stopPlayback();
}
public void cleanUpResources() {
stopPlayback();
}
//BandwidthMeter.EventListener implementation
@Override
public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
if (mReportBandwidth) {
eventEmitter.bandwidthReport(bitrate);
}
}
// Internal methods
/**
* Toggling the visibility of the player control view
*/
private void togglePlayerControlVisibility() {
if(player == null) return;
reLayout(playerControlView);
if (playerControlView.isVisible()) {
playerControlView.hide();
} else {
playerControlView.show();
}
}
/**
* Initializing Player control
*/
private void initializePlayerControl() {
if (playerControlView == null) {
playerControlView = new PlayerControlView(getContext());
}
// Setting the player for the playerControlView
playerControlView.setPlayer(player);
playerControlView.show();
playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container);
// Invoking onClick event for exoplayerView
exoPlayerView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
togglePlayerControlVisibility();
}
});
//Handling the playButton click event
ImageButton playButton = playerControlView.findViewById(R.id.exo_play);
playButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (player != null && player.getPlaybackState() == Player.STATE_ENDED) {
player.seekTo(0);
}
setPausedModifier(false);
}
});
//Handling the pauseButton click event
ImageButton pauseButton = playerControlView.findViewById(R.id.exo_pause);
pauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setPausedModifier(true);
}
});
//Handling the fullScreenButton click event
FrameLayout fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen_button);
fullScreenButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setFullscreen(!isFullscreen);
}
});
updateFullScreenIcon(isFullscreen);
// Invoking onPlayerStateChanged event for Player
eventListener = new Player.EventListener() {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
reLayout(playPauseControlContainer);
//Remove this eventListener once its executed. since UI will work fine once after the reLayout is done
player.removeListener(eventListener);
}
};
player.addListener(eventListener);
}
/**
* Adding Player control to the frame layout
*/
private void addPlayerControl() {
if(player == null) return;
LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
playerControlView.setLayoutParams(layoutParams);
int indexOfPC = indexOfChild(playerControlView);
if (indexOfPC != -1) {
removeViewAt(indexOfPC);
}
addView(playerControlView, 1, layoutParams);
}
/**
* Update fullscreen icon
*/
private void updateFullScreenIcon(Boolean fullScreen) {
if(playerControlView != null && player != null) {
//Play the video whenever the user clicks minimize or maximise button. In order to enable the controls
player.setPlayWhenReady(!isPaused);
ImageView fullScreenIcon = playerControlView.findViewById(R.id.exo_fullscreen_icon);
if (fullScreen) {
fullScreenIcon.setImageResource(R.drawable.fullscreen_shrink);
} else {
fullScreenIcon.setImageResource(R.drawable.fullscreen_expand);
}
}
}
/**
* Enable or Disable fullscreen button
*/
private void enableFullScreenButton(Boolean enable) {
if(playerControlView != null) {
FrameLayout fullScreenButton = playerControlView.findViewById(R.id.exo_fullscreen_button);
fullScreenButton.setAlpha(enable ? 1.0f : 0.5f);
fullScreenButton.setEnabled(enable);
}
}
/**
* Update the layout
* @param view view needs to update layout
*
* This is a workaround for the open bug in react-native: https://github.com/facebook/react-native/issues/17968
*/
private void reLayout(View view) {
if (view == null) return;
view.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight());
}
private void initializePlayer() {
ReactExoplayerView self = this;
// This ensures all props have been settled, to avoid async racing conditions.
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (player == null) {
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
trackSelector.setParameters(trackSelector.buildUponParameters()
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
DefaultLoadControl.Builder defaultLoadControlBuilder = new DefaultLoadControl.Builder();
defaultLoadControlBuilder.setAllocator(allocator);
defaultLoadControlBuilder.setBufferDurationsMs(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs);
defaultLoadControlBuilder.setTargetBufferBytes(-1);
defaultLoadControlBuilder.setPrioritizeTimeOverSizeThresholds(true);
DefaultLoadControl defaultLoadControl = defaultLoadControlBuilder.createDefaultLoadControl();
DefaultRenderersFactory renderersFactory =
new DefaultRenderersFactory(getContext())
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
// TODO: Add drmSessionManager to 5th param from: https://github.com/react-native-community/react-native-video/pull/1445
player = ExoPlayerFactory.newSimpleInstance(getContext(), renderersFactory,
trackSelector, defaultLoadControl, null, bandwidthMeter);
player.addListener(self);
player.addMetadataOutput(self);
exoPlayerView.setPlayer(player);
audioBecomingNoisyReceiver.setListener(self);
bandwidthMeter.addEventListener(new Handler(), self);
setPlayWhenReady(!isPaused);
playerNeedsSource = true;
PlaybackParameters params = new PlaybackParameters(rate, 1f);
player.setPlaybackParameters(params);
}
if (playerNeedsSource && srcUri != null) {
ArrayList<MediaSource> mediaSourceList = buildTextSources();
MediaSource videoSource = buildMediaSource(srcUri, extension);
MediaSource mediaSource;
if (mediaSourceList.size() == 0) {
mediaSource = videoSource;
} else {
mediaSourceList.add(0, videoSource);
MediaSource[] textSourceArray = mediaSourceList.toArray(
new MediaSource[mediaSourceList.size()]
);
mediaSource = new MergingMediaSource(textSourceArray);
}
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
if (haveResumePosition) {
player.seekTo(resumeWindow, resumePosition);
}
player.prepare(mediaSource, !haveResumePosition, false);
playerNeedsSource = false;
eventEmitter.loadStart();
loadVideoStarted = true;
}
// Initializing the playerControlView
initializePlayerControl();
setControls(controls);
applyModifiers();
}
}, 1);
}
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
: uri.getLastPathSegment());
switch (type) {
case C.TYPE_SS:
return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false)
).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
case C.TYPE_DASH:
return new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
buildDataSourceFactory(false)
).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
case C.TYPE_HLS:
return new HlsMediaSource.Factory(
mediaDataSourceFactory
).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
case C.TYPE_OTHER:
return new ProgressiveMediaSource.Factory(
mediaDataSourceFactory
).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
default: {
throw new IllegalStateException("Unsupported type: " + type);
}
}
}
private ArrayList<MediaSource> buildTextSources() {
ArrayList<MediaSource> textSources = new ArrayList<>();
if (textTracks == null) {
return textSources;
}
for (int i = 0; i < textTracks.size(); ++i) {
ReadableMap textTrack = textTracks.getMap(i);
String language = textTrack.getString("language");
String title = textTrack.hasKey("title")
? textTrack.getString("title") : language + " " + i;
Uri uri = Uri.parse(textTrack.getString("uri"));
MediaSource textSource = buildTextSource(title, uri, textTrack.getString("type"),
language);
if (textSource != null) {
textSources.add(textSource);
}
}
return textSources;
}
private MediaSource buildTextSource(String title, Uri uri, String mimeType, String language) {
Format textFormat = Format.createTextSampleFormat(title, mimeType, Format.NO_VALUE, language);
return new SingleSampleMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(uri, textFormat, C.TIME_UNSET);
}
private void releasePlayer() {
if (player != null) {
updateResumePosition();
player.release();
player.removeMetadataOutput(this);
trackSelector = null;
player = null;
}
progressHandler.removeMessages(SHOW_PROGRESS);
themedReactContext.removeLifecycleEventListener(this);
audioBecomingNoisyReceiver.removeListener();
bandwidthMeter.removeEventListener(this);
}
private boolean requestAudioFocus() {
if (disableFocus || srcUri == null) {
return true;
}
int result = audioManager.requestAudioFocus(this,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
private void setPlayWhenReady(boolean playWhenReady) {
if (player == null) {
return;
}
if (playWhenReady) {
boolean hasAudioFocus = requestAudioFocus();
if (hasAudioFocus) {
player.setPlayWhenReady(true);
}
} else {
player.setPlayWhenReady(false);
}
}
private void startPlayback() {
if (player != null) {
switch (player.getPlaybackState()) {
case Player.STATE_IDLE:
case Player.STATE_ENDED:
initializePlayer();
break;
case Player.STATE_BUFFERING:
case Player.STATE_READY:
if (!player.getPlayWhenReady()) {
setPlayWhenReady(true);
}
break;
default:
break;
}
} else {
initializePlayer();
}
if (!disableFocus) {
setKeepScreenOn(true);
}
}
private void pausePlayback() {
if (player != null) {
if (player.getPlayWhenReady()) {
setPlayWhenReady(false);
}
}
setKeepScreenOn(false);
}
private void stopPlayback() {
onStopPlayback();
releasePlayer();
}
private void onStopPlayback() {
if (isFullscreen) {
//When the video stopPlayback.
//If the video is in fullscreen, then we will update the video to normal mode.
setFullscreen(!isFullscreen);
}
setKeepScreenOn(false);
audioManager.abandonAudioFocus(this);
enableFullScreenButton(false);
}
private void updateResumePosition() {
resumeWindow = player.getCurrentWindowIndex();
resumePosition = player.isCurrentWindowSeekable() ? Math.max(0, player.getCurrentPosition())
: C.TIME_UNSET;
}
private void clearResumePosition() {
resumeWindow = C.INDEX_UNSET;
resumePosition = C.TIME_UNSET;
}
/**
* Returns a new DataSource factory.
*
* @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new
* DataSource factory.
* @return A new DataSource factory.
*/
private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
return DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext,
useBandwidthMeter ? bandwidthMeter : null, requestHeaders);
}
// AudioManager.OnAudioFocusChangeListener implementation
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
eventEmitter.audioFocusChanged(false);
pausePlayback();
audioManager.abandonAudioFocus(this);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
eventEmitter.audioFocusChanged(false);
break;
case AudioManager.AUDIOFOCUS_GAIN:
eventEmitter.audioFocusChanged(true);
break;
default:
break;
}
if (player != null) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume
if (!muted) {
player.setVolume(audioVolume * 0.8f);
}
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal
if (!muted) {
player.setVolume(audioVolume * 1);
}
}
}
}
// AudioBecomingNoisyListener implementation
@Override
public void onAudioBecomingNoisy() {
eventEmitter.audioBecomingNoisy();
}
// Player.EventListener implementation
@Override
public void onLoadingChanged(boolean isLoading) {
// Do nothing.
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState=";
switch (playbackState) {
case Player.STATE_IDLE:
text += "idle";
eventEmitter.idle();
clearProgressMessageHandler();
break;
case Player.STATE_BUFFERING:
text += "buffering";
onBuffering(true);
clearProgressMessageHandler();
break;
case Player.STATE_READY:
text += "ready";
eventEmitter.ready();
onBuffering(false);
startProgressHandler();
videoLoaded();
// Setting the visibility for the playerControlView
if (playerControlView != null) {
playerControlView.show();
}
enableFullScreenButton(true);
break;
case Player.STATE_ENDED:
text += "ended";
eventEmitter.end();
onStopPlayback();
break;
default:
text += "unknown";
break;
}
Log.d(TAG, text);
}
private void startProgressHandler() {
progressHandler.sendEmptyMessage(SHOW_PROGRESS);
}
/*
The progress message handler will duplicate recursions of the onProgressMessage handler
on change of player state from any state to STATE_READY with playWhenReady is true (when
the video is not paused). This clears all existing messages.
*/
private void clearProgressMessageHandler() {
progressHandler.removeMessages(SHOW_PROGRESS);
}
private void videoLoaded() {
if (loadVideoStarted) {
loadVideoStarted = false;
setSelectedAudioTrack(audioTrackType, audioTrackValue);
setSelectedVideoTrack(videoTrackType, videoTrackValue);
setSelectedTextTrack(textTrackType, textTrackValue);
Format videoFormat = player.getVideoFormat();
int width = videoFormat != null ? videoFormat.width : 0;
int height = videoFormat != null ? videoFormat.height : 0;
eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height,
getAudioTrackInfo(), getTextTrackInfo(), getVideoTrackInfo());
}
}
private WritableArray getAudioTrackInfo() {
WritableArray audioTracks = Arguments.createArray();
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
int index = getTrackRendererIndex(C.TRACK_TYPE_AUDIO);
if (info == null || index == C.INDEX_UNSET) {
return audioTracks;
}
TrackGroupArray groups = info.getTrackGroups(index);
for (int i = 0; i < groups.length; ++i) {
Format format = groups.get(i).getFormat(0);
WritableMap audioTrack = Arguments.createMap();
audioTrack.putInt("index", i);
audioTrack.putString("title", format.id != null ? format.id : "");
audioTrack.putString("type", format.sampleMimeType);
audioTrack.putString("language", format.language != null ? format.language : "");
audioTrack.putString("bitrate", format.bitrate == Format.NO_VALUE ? ""
: String.format(Locale.US, "%.2fMbps", format.bitrate / 1000000f));
audioTracks.pushMap(audioTrack);
}
return audioTracks;
}
private WritableArray getVideoTrackInfo() {
WritableArray videoTracks = Arguments.createArray();
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
int index = getTrackRendererIndex(C.TRACK_TYPE_VIDEO);
if (info == null || index == C.INDEX_UNSET) {
return videoTracks;
}
TrackGroupArray groups = info.getTrackGroups(index);
for (int i = 0; i < groups.length; ++i) {
TrackGroup group = groups.get(i);
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
Format format = group.getFormat(trackIndex);
WritableMap videoTrack = Arguments.createMap();
videoTrack.putInt("width", format.width == Format.NO_VALUE ? 0 : format.width);
videoTrack.putInt("height",format.height == Format.NO_VALUE ? 0 : format.height);
videoTrack.putInt("bitrate", format.bitrate == Format.NO_VALUE ? 0 : format.bitrate);
videoTrack.putString("codecs", format.codecs != null ? format.codecs : "");
videoTrack.putString("trackId",
format.id == null ? String.valueOf(trackIndex) : format.id);
videoTracks.pushMap(videoTrack);
}
}
return videoTracks;
}
private WritableArray getTextTrackInfo() {
WritableArray textTracks = Arguments.createArray();
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
int index = getTrackRendererIndex(C.TRACK_TYPE_TEXT);
if (info == null || index == C.INDEX_UNSET) {
return textTracks;
}
TrackGroupArray groups = info.getTrackGroups(index);
for (int i = 0; i < groups.length; ++i) {
Format format = groups.get(i).getFormat(0);
WritableMap textTrack = Arguments.createMap();
textTrack.putInt("index", i);
textTrack.putString("title", format.id != null ? format.id : "");
textTrack.putString("type", format.sampleMimeType);
textTrack.putString("language", format.language != null ? format.language : "");
textTracks.pushMap(textTrack);
}
return textTracks;
}
private void onBuffering(boolean buffering) {
if (isBuffering == buffering) {
return;
}
isBuffering = buffering;
if (buffering) {
eventEmitter.buffering(true);
} else {
eventEmitter.buffering(false);
}
}
@Override
public void onPositionDiscontinuity(int reason) {
if (playerNeedsSource) {
// This will only occur if the user has performed a seek whilst in the error state. Update the
// resume position so that if the user then retries, playback will resume from the position to
// which they seeked.
updateResumePosition();
}
// When repeat is turned on, reaching the end of the video will not cause a state change
// so we need to explicitly detect it.
if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
eventEmitter.end();
}
}
@Override
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
// Do nothing.
}
@Override
public void onSeekProcessed() {
eventEmitter.seek(player.getCurrentPosition(), seekTime);
seekTime = C.TIME_UNSET;
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
// Do nothing.
}
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
// Do Nothing.
}
@Override
public void onPlaybackParametersChanged(PlaybackParameters params) {
eventEmitter.playbackRateChange(params.speed);
}
@Override
public void onPlayerError(ExoPlaybackException e) {
String errorString = null;
Exception ex = e;
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
Exception cause = e.getRendererException();
if (cause instanceof MediaCodecRenderer.DecoderInitializationException) {
// Special case for decoder initialization failures.
MediaCodecRenderer.DecoderInitializationException decoderInitializationException =
(MediaCodecRenderer.DecoderInitializationException) cause;
if (decoderInitializationException.decoderName == null) {
if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) {
errorString = getResources().getString(R.string.error_querying_decoders);
} else if (decoderInitializationException.secureDecoderRequired) {
errorString = getResources().getString(R.string.error_no_secure_decoder,
decoderInitializationException.mimeType);
} else {
errorString = getResources().getString(R.string.error_no_decoder,
decoderInitializationException.mimeType);
}
} else {
errorString = getResources().getString(R.string.error_instantiating_decoder,
decoderInitializationException.decoderName);
}
}
}
else if (e.type == ExoPlaybackException.TYPE_SOURCE) {
ex = e.getSourceException();
errorString = getResources().getString(R.string.unrecognized_media_format);
}
if (errorString != null) {
eventEmitter.error(errorString, ex);
}
playerNeedsSource = true;
if (isBehindLiveWindow(e)) {
clearResumePosition();
initializePlayer();
} else {
updateResumePosition();
}
}
private static boolean isBehindLiveWindow(ExoPlaybackException e) {
if (e.type != ExoPlaybackException.TYPE_SOURCE) {
return false;
}
Throwable cause = e.getSourceException();
while (cause != null) {
if (cause instanceof BehindLiveWindowException) {
return true;
}
cause = cause.getCause();
}
return false;
}
public int getTrackRendererIndex(int trackType) {
int rendererCount = player.getRendererCount();
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
if (player.getRendererType(rendererIndex) == trackType) {
return rendererIndex;
}
}
return C.INDEX_UNSET;
}
@Override
public void onMetadata(Metadata metadata) {
eventEmitter.timedMetadata(metadata);
}
// ReactExoplayerViewManager public api
public void setSrc(final Uri uri, final String extension, Map<String, String> headers) {
if (uri != null) {
boolean isOriginalSourceNull = srcUri == null;
boolean isSourceEqual = uri.equals(srcUri);
this.srcUri = uri;
this.extension = extension;
this.requestHeaders = headers;
this.mediaDataSourceFactory =
DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter,
this.requestHeaders);
if (!isOriginalSourceNull && !isSourceEqual) {
reloadSource();
}
}
}
public void setProgressUpdateInterval(final float progressUpdateInterval) {
mProgressUpdateInterval = progressUpdateInterval;
}
public void setReportBandwidth(boolean reportBandwidth) {
mReportBandwidth = reportBandwidth;
}
public void setRawSrc(final Uri uri, final String extension) {
if (uri != null) {
boolean isOriginalSourceNull = srcUri == null;
boolean isSourceEqual = uri.equals(srcUri);
this.srcUri = uri;
this.extension = extension;
this.mediaDataSourceFactory = buildDataSourceFactory(true);
if (!isOriginalSourceNull && !isSourceEqual) {
reloadSource();
}
}
}
public void setTextTracks(ReadableArray textTracks) {
this.textTracks = textTracks;
reloadSource();
}
private void reloadSource() {
playerNeedsSource = true;
initializePlayer();
}
public void setResizeModeModifier(@ResizeMode.Mode int resizeMode) {
exoPlayerView.setResizeMode(resizeMode);
}
private void applyModifiers() {
setRepeatModifier(repeat);
setMutedModifier(muted);
}
public void setRepeatModifier(boolean repeat) {
if (player != null) {
if (repeat) {
player.setRepeatMode(Player.REPEAT_MODE_ONE);
} else {
player.setRepeatMode(Player.REPEAT_MODE_OFF);
}
}
this.repeat = repeat;
}
public void setSelectedTrack(int trackType, String type, Dynamic value) {
if (player == null) return;
int rendererIndex = getTrackRendererIndex(trackType);
if (rendererIndex == C.INDEX_UNSET) {
return;
}
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
if (info == null) {
return;
}
TrackGroupArray groups = info.getTrackGroups(rendererIndex);
int groupIndex = C.INDEX_UNSET;
int[] tracks = {0} ;
if (TextUtils.isEmpty(type)) {
type = "default";
}
DefaultTrackSelector.Parameters disableParameters = trackSelector.getParameters()
.buildUpon()
.setRendererDisabled(rendererIndex, true)
.build();
if (type.equals("disabled")) {
trackSelector.setParameters(disableParameters);
return;
} else if (type.equals("language")) {
for (int i = 0; i < groups.length; ++i) {
Format format = groups.get(i).getFormat(0);
if (format.language != null && format.language.equals(value.asString())) {
groupIndex = i;
break;
}
}
} else if (type.equals("title")) {
for (int i = 0; i < groups.length; ++i) {
Format format = groups.get(i).getFormat(0);
if (format.id != null && format.id.equals(value.asString())) {
groupIndex = i;
break;
}
}
} else if (type.equals("index")) {
if (value.asInt() < groups.length) {
groupIndex = value.asInt();
}
} else if (type.equals("resolution")) {
int height = value.asInt();
for (int i = 0; i < groups.length; ++i) { // Search for the exact height
TrackGroup group = groups.get(i);
for (int j = 0; j < group.length; j++) {
Format format = group.getFormat(j);
if (format.height == height) {
groupIndex = i;
tracks[0] = j;
break;
}
}
}
} else if (rendererIndex == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default
// Use system settings if possible
CaptioningManager captioningManager
= (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE);
if (captioningManager != null && captioningManager.isEnabled()) {
groupIndex = getGroupIndexForDefaultLocale(groups);
}
} else if (rendererIndex == C.TRACK_TYPE_AUDIO) { // Audio default
groupIndex = getGroupIndexForDefaultLocale(groups);
}
if (groupIndex == C.INDEX_UNSET && trackType == C.TRACK_TYPE_VIDEO && groups.length != 0) { // Video auto
// Add all tracks as valid options for ABR to choose from
TrackGroup group = groups.get(0);
tracks = new int[group.length];
groupIndex = 0;
for (int j = 0; j < group.length; j++) {
tracks[j] = j;
}
}
if (groupIndex == C.INDEX_UNSET) {
trackSelector.setParameters(disableParameters);
return;
}
DefaultTrackSelector.Parameters selectionParameters = trackSelector.getParameters()
.buildUpon()
.setRendererDisabled(rendererIndex, false)
.setSelectionOverride(rendererIndex, groups,
new DefaultTrackSelector.SelectionOverride(groupIndex, tracks))
.build();
trackSelector.setParameters(selectionParameters);
}
private int getGroupIndexForDefaultLocale(TrackGroupArray groups) {
if (groups.length == 0){
return C.INDEX_UNSET;
}
int groupIndex = 0; // default if no match
String locale2 = Locale.getDefault().getLanguage(); // 2 letter code
String locale3 = Locale.getDefault().getISO3Language(); // 3 letter code
for (int i = 0; i < groups.length; ++i) {
Format format = groups.get(i).getFormat(0);
String language = format.language;
if (language != null && (language.equals(locale2) || language.equals(locale3))) {
groupIndex = i;
break;
}
}
return groupIndex;
}
public void setSelectedVideoTrack(String type, Dynamic value) {
videoTrackType = type;
videoTrackValue = value;
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
}
public void setSelectedAudioTrack(String type, Dynamic value) {
audioTrackType = type;
audioTrackValue = value;
setSelectedTrack(C.TRACK_TYPE_AUDIO, audioTrackType, audioTrackValue);
}
public void setSelectedTextTrack(String type, Dynamic value) {
textTrackType = type;
textTrackValue = value;
setSelectedTrack(C.TRACK_TYPE_TEXT, textTrackType, textTrackValue);
}
public void setPausedModifier(boolean paused) {
isPaused = paused;
if (player != null) {
if (!paused) {
startPlayback();
} else {
pausePlayback();
}
}
}
public void setMutedModifier(boolean muted) {
this.muted = muted;
audioVolume = muted ? 0.f : 1.f;
if (player != null) {
player.setVolume(audioVolume);
}
}
public void setVolumeModifier(float volume) {
audioVolume = volume;
if (player != null) {
player.setVolume(audioVolume);
}
}
public void seekTo(long positionMs) {
if (player != null) {
seekTime = positionMs;
player.seekTo(positionMs);
}
}
public void setRateModifier(float newRate) {
rate = newRate;
if (player != null) {
PlaybackParameters params = new PlaybackParameters(rate, 1f);
player.setPlaybackParameters(params);
}
}
public void setMaxBitRateModifier(int newMaxBitRate) {
maxBitRate = newMaxBitRate;
if (player != null) {
trackSelector.setParameters(trackSelector.buildUponParameters()
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
}
}
public void setMinLoadRetryCountModifier(int newMinLoadRetryCount) {
minLoadRetryCount = newMinLoadRetryCount;
releasePlayer();
initializePlayer();
}
public void setPlayInBackground(boolean playInBackground) {
this.playInBackground = playInBackground;
}
public void setDisableFocus(boolean disableFocus) {
this.disableFocus = disableFocus;
}
// todo
private final Runnable measureAndLayout =
new Runnable() {
@Override
public void run() {
measure(
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
}
};
// todo
@Override
public void requestLayout() {
super.requestLayout();
// The spinner relies on a measure + layout pass happening after it calls requestLayout().
// Without this, the widget never actually changes the selection and doesn't call the
// appropriate listeners. Since we override onLayout in our ViewGroups, a layout pass never
// happens after a call to requestLayout, so we simulate one here.
post(measureAndLayout);
}
// todo
public void setFullscreen(boolean fullscreen) {
if (fullscreen == isFullscreen) {
return; // Avoid generating events when nothing is changing
}
updateFullScreenIcon(fullscreen);
isFullscreen = fullscreen;
Activity activity = themedReactContext.getCurrentActivity();
if (activity == null) {
return;
}
Window window = activity.getWindow();
View decorView = window.getDecorView();
int uiOptions;
if (isFullscreen) {
// if (Util.SDK_INT >= 19) { // 4.4+
// uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION
// | SYSTEM_UI_FLAG_IMMERSIVE_STICKY
// | SYSTEM_UI_FLAG_FULLSCREEN;
// } else {
// uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION
// | SYSTEM_UI_FLAG_FULLSCREEN;
// }
// activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
// eventEmitter.fullscreenWillPresent();
// decorView.setSystemUiVisibility(uiOptions);
// eventEmitter.fullscreenDidPresent();
// decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN
// |View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
// |View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
eventEmitter.fullscreenWillPresent();
DisplayMetrics metrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(exoPlayerView.getLayoutParams());
params.width = metrics.heightPixels;
params.height = metrics.widthPixels;
params.leftMargin = 0;
// params.width = params.MATCH_PARENT;
// params.height = params.MATCH_PARENT;
this.setLayoutParams(params);
exoPlayerView.setLayoutParams(params);
// this.requestLayout();
FrameLayout.LayoutParams exoParams = (FrameLayout.LayoutParams) exoPlayerView.getLayoutParams();
Log.d("长度", Integer.toString(exoParams.width));
Log.d("宽度", Integer.toString(exoParams.height));
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
exoPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL);
isFullscreen = true;
eventEmitter.fullscreenDidPresent();
} else {
// uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
// //orientation, 1 is for Portrait and 2 for Landscape.
// if(this.initialOrientation == 1)
// activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// else
// activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
// eventEmitter.fullscreenWillDismiss();
// decorView.setSystemUiVisibility(uiOptions);
// eventEmitter.fullscreenDidDismiss();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
eventEmitter.fullscreenWillDismiss();
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) exoPlayerView.getLayoutParams();
// params.width = (int) ( getResources().getDisplayMetrics().widthPixels);
// params.height = (int) ( getResources().getDisplayMetrics().heightPixels);
params.width = params.MATCH_PARENT;
params.height = params.MATCH_PARENT;
Log.d("长度", Integer.toString(params.width));
Log.d("宽度", Integer.toString(params.height));
exoPlayerView.setLayoutParams(params);
isFullscreen = false;
eventEmitter.fullscreenDidDismiss();
}
}
public void setUseTextureView(boolean useTextureView) {
// exoPlayerView.setUseTextureView(useTextureView);
}
public void setHideShutterView(boolean hideShutterView) {
// exoPlayerView.setHideShutterView(hideShutterView);
}
public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs) {
minBufferMs = newMinBufferMs;
maxBufferMs = newMaxBufferMs;
bufferForPlaybackMs = newBufferForPlaybackMs;
bufferForPlaybackAfterRebufferMs = newBufferForPlaybackAfterRebufferMs;
releasePlayer();
initializePlayer();
}
/**
* Handling controls prop
*
* @param controls Controls prop, if true enable controls, if false disable them
*/
public void setControls(boolean controls) {
this.controls = controls;
if (player == null || exoPlayerView == null) return;
if (controls) {
addPlayerControl();
} else {
int indexOfPC = indexOfChild(playerControlView);
if (indexOfPC != -1) {
removeViewAt(indexOfPC);
}
}
}
}
不会搞搞什么小程序?封闭起来让大家都听你的话是吧
这也没有那也没有
没有FormData、没有Blob、uploadFile不能获取到文件名
社区客服要么不鸟你,要么回答永远是一句话:不支持,再追问的话不可能再回答了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CSS3无限滚动</title>
<style type="text/css">
@-webkit-keyframes rowup {
0% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
100% {
-webkit-transform: translate3d(0, -312px, 0);
transform: translate3d(0, -312px, 0);
}
}
@keyframes rowup {
0% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
100% {
-webkit-transform: translate3d(0, -312px, 0);
transform: translate3d(0, -312px, 0);
}
}
.list{
width: 400px;
border: 1px solid #999;
margin-top: 20px;
position: relative;
height: 200px;
overflow: hidden;
}
.list .rowup{
-webkit-animation: 10s rowup linear infinite normal;
animation: 10s rowup linear infinite normal;
position: relative;
}
</style>
</head>
<body>
<h1>用CSS3实现无限循环的无缝滚动</h1>
<p>返回文章: <a href="http://www.xiabingbao.com/css3/2017/07/03/css3-infinite-scroll.html">用CSS3实现无限循环的无缝滚动</a></p>
<p>使用js随机生成数据 | <a href="javascript:window.location.reload()">刷新页面观察不同数据</a></p>
<div class="list">
<div class="cc">
</div>
</div>
</body>
<script type="text/javascript">
// 设置keyframes属性
function addKeyFrames(y){
var style = document.createElement('style');
style.type = 'text/css';
var keyFrames = '\
@-webkit-keyframes rowup {\
0% {\
-webkit-transform: translate3d(0, 0, 0);\
transform: translate3d(0, 0, 0);\
}\
100% {\
-webkit-transform: translate3d(0, A_DYNAMIC_VALUE, 0);\
transform: translate3d(0, A_DYNAMIC_VALUE, 0);\
}\
}\
@keyframes rowup {\
0% {\
-webkit-transform: translate3d(0, 0, 0);\
transform: translate3d(0, 0, 0);\
}\
100% {\
-webkit-transform: translate3d(0, A_DYNAMIC_VALUE, 0);\
transform: translate3d(0, A_DYNAMIC_VALUE, 0);\
}\
}';
style.innerHTML = keyFrames.replace(/A_DYNAMIC_VALUE/g, y);
document.getElementsByTagName('head')[0].appendChild(style);
}
function init(){
var data = '塞下秋来风景异,衡阳雁去无留意。四面边声连角起,千嶂里,长烟落日孤城闭。浊酒一杯家万里,燕然未勒归无计。羌管悠悠霜满地,人不寐,将军白发征夫泪。', //样例数据
data_len = data.length,
len = parseInt(Math.random()*6)+6, // 数据的长度
html = '<div class="ss">';
for(var i=0; i<len; i++){
var start = parseInt( Math.random()*(data_len-20) ),
s = parseInt( Math.random()*data_len );
html += '<div class="item"v>'+i+'- '+data.substr(start, s)+'</div>';
}
html += '</div>';
document.querySelector('.list .cc').innerHTML = html+html; // 复制一份数据
var height = document.querySelector('.list .ss').offsetHeight; // 一份数据的高度
addKeyFrames( '-'+height+'px' ); // 设置keyframes
document.querySelector('.list .cc').className += ' rowup'; // 添加 rowup
}
init();
</script>
</html>
小程序里没有FormData
类,所以POST方法如果要传multipart/form-data就会报错。
而npm上的formdata-polyfill
,类似 https://github.com/form-data/form-data 或者https://github.com/jimmywarting/FormData
都不好用
好在有人探究出来 https://developers.weixin.qq.com/community/develop/article/doc/0000cc0e5bc5d093c6f8be17254c13 可以手动拼装出FormData
格式的字符串,并且成功调用。
以下附代码:
// 手动拼接FormData字符串
// 函数边界处理没怎么做,各位可自行补充
function createFormData(params = {}, boundary = '') {
let result = '';
for (let i in params) {
result += `\r\n--${boundary}`;
result += `\r\nContent-Disposition: form-data; name="${i}"`;
result += '\r\n';
result += `\r\n${params[i]}`
}
// 如果obj不为空,则最后一行加上boundary
if (result) {
result += `\r\n--${boundary}`
}
return result
}
// 通用post请求
export const post = function (url, params) {
return new Promise(function (resolve, reject) {
// 生成一个boundary字符串
const boundary = `----FooBar${new Date().getTime()}`;
const formData = createFormData(params, boundary);
console.log(formData);
Taro.request({ // 这里我用的taro,改成wx.request也一样
url,
method: 'POST',
credentials: 'include', //设置传递cookies
dataType: 'json',
header: {
'Accept': 'application/json',
'Content-Type': `multipart/form-data; boundary=${boundary}`,
},
data: formData,
timeout: 5000,
success: function (res) {
resolve(res.data);
},
fail: function (error) {
reject(error);
}
})
});
}
有翻q软件的情况:
看看你的梯子的ip和端口是多少,然后运行:
git config --global http.proxy http://127.0.0.1:59577
git config --global https.proxy http://127.0.0.1:59577
完事之后再运行:
git config --global --unset http.proxy
git config --global --unset https.proxy
Taro v3.0.7
config/index.js的配置是:h5.publicPath = '/',h5.router.mode = 'browser'(此为前提)
我运行npm run build:h5
,得到dist/
文件夹,直接打开dist/index.html
文件,控制台会报错Route not found
以为是taro的缺陷,无奈,愤怒,愁眉苦脸
抱着试一试的态度,在qq群Taro(京东)多端开发2群(群号:907665795)里提出了这个问题,有大佬回答:“其实放服务器就可以了 每次返回index”
震惊,无法理解,直接访问index.html
的报错,放到服务器上就会消失吗?但我没有别的选择,有枣没枣打一杆子吧。
然后,我总不能在服务器上试来试去吧,我想服务器也不过是起个nginx的事儿,那我在本地电脑装个nginx,模拟一下试试看好了。
安装nginx,没有比 https://www.cnblogs.com/tandaxia/p/8810648.html 说得更清楚的了。
小tips:brew install nginx
的过程中会卡住,ctrl + c
结束掉,然后重新brew install nginx
即可。
安装完毕后,命令行运行nginx
,然后浏览器访问localhost:8080
,出现nginx的页面即为安装成功
然后按照上面安装教程里说的,配置nginx(我这里不多细说,按照上面安装教程完整地设置就可以了):
user root owner;
#.....省略中间
server {
listen 5188;
server_name localhost;
location / {
root /Users/workspace/company/new-time-center/dist;
index index.html index.htm;
}
}
配置好之后,运行sudo nginx
(如果已经启动nginx,运行sudo nginx -s reload
),访问localhost:5188
,瞬间流下感动的泪水,大佬诚不欺我,确实放到服务器上就好了。
在页面上点了几次,我发现了一个新的问题,直接访问http://localhost:5188
是正常的,但是在某个页面刷新,或者直接访问某个路径(如http://localhost:5188/pages/user/index
就报404了)
询问大佬,大佬回答“适配一下 访问路由 都把 index.html 返回给前端,访问任何路径 后端路由返回index.html 即可”
“不像hash 方便 browser必须要服务器配置一下”
大概明白,访问http://localhost:5188
时,nginx知道去找index.html
,但访问/pages/*
等路径时,由于没有配置,所以就404了。
在网上谷歌了一下,最终nginx的server的配置是:
server {
listen 5188;
server_name localhost;
location / {
root /Users/luyang/workspace/company/new-time-center/dist;
index index.html index.htm;
}
location /pages {
alias /Users/luyang/workspace/company/new-time-center/dist;
try_files $uri $uri/ /index.html;
}
}
这样直接输入地址或者刷新都可以显示对应页面了。
至此,Taro build:h5并部署到服务器上遇到的问题就都解决了
'use strict';
import React, { useState, useEffect, forwardRef } from 'react';
import { StyleSheet, Platform, ViewPropTypes } from 'react-native';
import PropTypes from 'prop-types';
import { WebView } from 'react-native-webview';
import { reduceData, getWidth, isSizeChanged, shouldUpdate } from './utils';
const AutoHeightWebView = React.memo(
forwardRef((props, ref) => {
const { style, onMessage, onSizeUpdated, scrollEnabledWithZoomedin, scrollEnabled, initialHeight } = props;
const innerRef = React.useRef(ref)
const [size, setSize] = useState({
height: style && style.height ? style.height : initialHeight,
width: getWidth(style)
});
const [scrollable, setScrollable] = useState(false);
const handleMessage = event => {
onMessage && onMessage(event);
if (!event.nativeEvent) {
return;
}
let data = {};
// Sometimes the message is invalid JSON, so we ignore that case
try {
data = JSON.parse(event.nativeEvent.data);
} catch (error) {
console.error(error);
return;
}
const { height, width, zoomedin } = data;
!scrollEnabled && scrollEnabledWithZoomedin && setScrollable(!!zoomedin);
const { height: previousHeight, width: previousWidth } = size;
isSizeChanged({ height, previousHeight, width, previousWidth }) &&
setSize({
height,
width
});
};
const currentScrollEnabled = scrollEnabled === false && scrollEnabledWithZoomedin ? scrollable : scrollEnabled;
const { currentSource, script } = reduceData(props);
const { width, height } = size;
useEffect(
() =>
onSizeUpdated &&
onSizeUpdated({
height,
width
}),
[width, height, onSizeUpdated]
);
return (
<WebView
{...props}
ref={innerRef}
onMessage={handleMessage}
style={[
styles.webView,
{
width,
height
},
style
]}
source={currentSource}
onLoadProgress={({ nativeEvent }) => {
console.log(nativeEvent.progress);
console.log(innerRef);
if (nativeEvent.progress === 1 && innerRef.current) {
innerRef.current.injectJavaScript(script);
}
}}
scrollEnabled={currentScrollEnabled}
/>
);
}),
(prevProps, nextProps) => !shouldUpdate({ prevProps, nextProps })
);
AutoHeightWebView.propTypes = {
onSizeUpdated: PropTypes.func,
files: PropTypes.arrayOf(
PropTypes.shape({
href: PropTypes.string,
type: PropTypes.string,
rel: PropTypes.string
})
),
style: ViewPropTypes.style,
customScript: PropTypes.string,
customStyle: PropTypes.string,
viewportContent: PropTypes.string,
scrollEnabledWithZoomedin: PropTypes.bool,
initialHeight: PropTypes.number,
// webview props
originWhitelist: PropTypes.arrayOf(PropTypes.string),
onMessage: PropTypes.func,
scalesPageToFit: PropTypes.bool,
source: PropTypes.object
};
let defaultProps = {
showsVerticalScrollIndicator: false,
showsHorizontalScrollIndicator: false,
initialHeight: 0,
originWhitelist: ['*']
};
Platform.OS === 'android' &&
Object.assign(defaultProps, {
scalesPageToFit: false
});
Platform.OS === 'ios' &&
Object.assign(defaultProps, {
viewportContent: 'width=device-width'
});
AutoHeightWebView.defaultProps = defaultProps;
const styles = StyleSheet.create({
webView: {
backgroundColor: 'transparent'
}
});
export default AutoHeightWebView;
sudo vi /etc/hosts
(把127.0.0.1改成你微信注册的域名)
(转发端口)
sudo vim /etc/pf.conf
在rdr-anchor "com.apple/*"
下,加上:rdr on lo0 inet proto tcp from any to 127.0.0.1 port 80 -> 127.0.0.1 port 8080
(这里8080改成你的服务的端口)
重新加载配置:sudo pfctl -f /etc/pf.conf
启动:sudo pfctl -e
有可能是因为SameSite策略导致。
前端域名在a.com,访问b.com的接口,前端的fetch请求设置了credentials: 'include',尽管服务器的response带着Set-Cookie,但是前端发请求的时候,并没有将cookie带给服务器端。这是因为在chrome上默认是SameSite=Lax,也就是不允许非同站传递cookie(非同站,指前端与服务器域名不同).
server {
listen 80;
server_name newtimecenter.gxrb.com.cn;
location / {
root /usr/node/newTimeCenter;
index index.html index.htm;
}
location /pages {
alias /usr/node/newTimeCenter;
try_files $uri $uri/ /index.html;
}
}
sprite转gif:
1、某个需要计算的固定值,用变量存起来,而不是每次都去算。
例如:
while (result.length < rowLength * colLength) {}
此条花费156ms
而
const size = rowLength * colLength;
while (result.length < size) {}
仅花费96ms
之前做过一个h5,是用createjs做的,虽然是Adobe出品,可以配合Animate.cc使用,但是年代久远,api可理解度也差,所以看看有没有更方便的h5 canvas库。网上搜了一下,有个叫fabric.js的东西,可以考虑一下。
xcode build项目,报错:unknown type name 'BN_ULONG'
解决:cocoapods版本是1.9.x,升到1.10.1就好了
记录一下吧。。。
谁能想到包管理器的版本会影响到build呢。。。。
1.pod install/pod update的时候,报react-native-clear-cache没有podspec
解决:参考 https://github.com/17554265585/react-native-clear-cache/blob/master/RNClearCache.podspec,在node_modules/下找到react-native-clear-cache ,新增一个文件叫RNClearCache.podspec,然后贴以下内容:
require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
Pod::Spec.new do |s|
s.name = "RNClearCache"
s.version = package['version']
s.summary = package['description']
s.license = package['license']
s.authors = package['author']
s.homepage = package['repository']['url']
s.platform = :ios, "9.0"
s.ios.deployment_target = '9.0'
s.tvos.deployment_target = '10.0'
s.source = { :git => "https://github.com/17554265585/react-native-clear-cache.git", :tag => "v#{s.version}" }
s.source_files = "ios/**/*.{h,m}"
s.dependency 'React'
end
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.