文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

关于Android HTML5 audio autoplay无效问题的解决方案

2022-06-06 07:34

关注

前言:在android HTML5 开发中有不少人遇到过 audio 标签 autoplay在某些设备上无效的问题,网上大多是讲怎么在js中操作,即在特定的时刻调用audio的play()方法,在android上还是无效。

一、解决方案

在android 4.2添加了允许用户手势触发音视频播放接口,该接口默认为 true ,即默认不允许自动播放音视频,只能是用户交互的方式由用户自己促发播放。


WebView webView = this.finishActivity(R.id.main_act_webview);
// ... ...
// 其他配置
// ... ...
// 设置4.2以后版本支持autoPlay,非用户手势促发
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
webView.getSettings().setMediaPlaybackRequiresUserGesture(false);
}

通过以上配置就可以加载带有自动播放的音视频啦!

二、 源码分析

下面我们沿着该问题来窥探下WebView的系统源码:

1、 通过getSettings()获取到的WebView的配置



public WebSettings getSettings() {
checkThread();
return mProvider.getSettings();
}

这里通过一个 mProvider来获取的配置信息,通过看WebView的源码,我们可以看到,WebView的所有操作都是交给 mProvider来进行的。

2、 mPeovider是在哪初始化的?



@SuppressWarnings("deprecation") // for super() call into deprecated base class constructor.
protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
super(context, attrs, defStyleAttr, defStyleRes);
if (context == null) {
throw new IllegalArgumentException("Invalid context argument");
}
sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
Build.VERSION_CODES.JELLY_BEAN_MR2;
checkThread();
ensureProviderCreated();
mProvider.init(javaScriptInterfaces, privateBrowsing);
// Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
CookieSyncManager.setGetInstanceIsAllowed();
}

可以看到有个ensureProviderCreated()方法,就是在这里创建的mProvider:


private void ensureProviderCreated() {
checkThread();
if (mProvider == null) {
// As this can get called during the base class constructor chain, pass the minimum
// number of dependencies here; the rest are deferred to init().
mProvider = getFactory().createWebView(this, new PrivateAccess());
}
}

OK,到此知道了mProvider是在WebView的构造函数中创建的,并且WebView的所有操作都是交给mProvider进行的。

3、 但是这个mPeovider到底是谁派来的呢?

看下WebViewFactory#getFactory()做了什么操作:


static WebViewFactoryProvider getProvider() {
synchronized (sProviderLock) {
// For now the main purpose of this function (and the factory abstraction) is to keep
// us honest and minimize usage of WebView internals when binding the proxy.
if (sProviderInstance != null) return sProviderInstance;
final int uid = android.os.Process.myUid();
if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID) {
throw new UnsupportedOperationException(
"For security reasons, WebView is not allowed in privileged processes");
}
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
try {
Class<WebViewFactoryProvider> providerClass = getProviderClass();
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
try {
sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
.newInstance(new WebViewDelegate());
if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
return sProviderInstance;
} catch (Exception e) {
Log.e(LOGTAG, "error instantiating provider", e);
throw new AndroidRuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
StrictMode.setThreadPolicy(oldPolicy);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
}
}

可见在23行返回了sProviderInstance, 是由 providerClass 通过反射创建的,15行中通过getProviderClass() 得到了providerClass.


private static Class<WebViewFactoryProvider> getProviderClass() {
try {
// First fetch the package info so we can log the webview package version.
sPackageInfo = fetchPackageInfo();
Log.i(LOGTAG, "Loading " + sPackageInfo.packageName + " version " +
sPackageInfo.versionName + " (code " + sPackageInfo.versionCode + ")");
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
loadNativeLibrary();
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
try {
return getChromiumProviderClass();
} catch (ClassNotFoundException e) {
Log.e(LOGTAG, "error loading provider", e);
throw new AndroidRuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (MissingWebViewPackageException e) {
// If the package doesn't exist, then try loading the null WebView instead.
// If that succeeds, then this is a device without WebView support; if it fails then
// swallow the failure, complain that the real WebView is missing and rethrow the
// original exception.
try {
return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
} catch (ClassNotFoundException e2) {
// Ignore.
}
Log.e(LOGTAG, "Chromium WebView package does not exist", e);
throw new AndroidRuntimeException(e);
}
}

主要的 14行 返回了一个 getChromiumProviderClass(); 是不是有点熟悉,没错Android在4.4开始使用强大的Chromium替换掉了原来的WebKit。来看下这个getChromiumProviderClass()。


// throws MissingWebViewPackageException
private static Class<WebViewFactoryProvider> getChromiumProviderClass()
throws ClassNotFoundException {
Application initialApplication = AppGlobals.getInitialApplication();
try {
// Construct a package context to load the Java code into the current app.
Context webViewContext = initialApplication.createPackageContext(
sPackageInfo.packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
initialApplication.getAssets().addAssetPath(
webViewContext.getApplicationInfo().sourceDir);
ClassLoader clazzLoader = webViewContext.getClassLoader();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
try {
return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY, true,
clazzLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
}
} catch (PackageManager.NameNotFoundException e) {
throw new MissingWebViewPackageException(e);
}
}

最后找到了这个 CHROMIUM_WEBVIEW_FACTORY, 可以看到在 WebViewFactory 中的定义:


private static final String CHROMIUM_WEBVIEW_FACTORY =
"com.android.webview.chromium.WebViewChromiumFactoryProvider";

回答2小节的mProvider的初始化,在WebViewChromiumFactoryProvider 的 createWebView(…) 中进行了mProvider的初始化:


@Override
public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess);
synchronized (mLock) {
if (mWebViewsToStart != null) {
mWebViewsToStart.add(new WeakReference<WebViewChromium>(wvc));
}
}
ResourceProvider.registerResources(webView.getContext());
return wvc;
}

OK,到这里就真正找到了mProvider 的真正初始化位置,其实它就是一个WebViewChromium,不要忘了我们为什么费这么大劲找mProvider,其实是为了分析 webView.getSettings(),这样就回到了第一小节,通过getSettings()获取到的WebView的配置。

4、 Settings的初始化

通过第一小节,我们知道Settings是mProvider的一个变量,要想找到Settings就要到 WebViewChromium 来看下:


@Override
public WebSettings getSettings() {
return mWebSettings;
}

接下来就是Settings初始化的地方啦


@Override
// BUG=6790250 |javaScriptInterfaces| was only ever used by the obsolete DumpRenderTree
// so is ignored. TODO: remove it from WebViewProvider.
public void init(final Map<String, Object> javaScriptInterfaces,
final boolean privateBrowsing) {
if (privateBrowsing) {
mFactory.startYourEngines(true);
final String msg = "Private browsing is not supported in WebView.";
if (mAppTargetSdkVersion >= Build.VERSION_CODES.KITKAT) {
throw new IllegalArgumentException(msg);
} else {
Log.w(TAG, msg);
TextView warningLabel = new TextView(mWebView.getContext());
warningLabel.setText(mWebView.getContext().getString(
com.android.internal.R.string.webviewchromium_private_browsing_warning));
mWebView.addView(warningLabel);
}
}
// We will defer real initialization until we know which thread to do it on, unless:
// - we are on the main thread already (common case),
// - the app is targeting >= JB MR2, in which case checkThread enforces that all usage
// comes from a single thread. (Note in JB MR2 this exception was in WebView.java).
if (mAppTargetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mFactory.startYourEngines(false);
checkThread();
} else if (!mFactory.hasStarted()) {
if (Looper.myLooper() == Looper.getMainLooper()) {
mFactory.startYourEngines(true);
}
}
final boolean isAccessFromFileURLsGrantedByDefault =
mAppTargetSdkVersion < Build.VERSION_CODES.JELLY_BEAN;
final boolean areLegacyQuirksEnabled =
mAppTargetSdkVersion < Build.VERSION_CODES.KITKAT;
mContentsClientAdapter = new WebViewContentsClientAdapter(mWebView);
mWebSettings = new ContentSettingsAdapter(new AwSettings(
mWebView.getContext(), isAccessFromFileURLsGrantedByDefault,
areLegacyQuirksEnabled));
mRunQueue.addTask(new Runnable() {
@Override
public void run() {
initForReal();
if (privateBrowsing) {
// Intentionally irreversibly disable the webview instance, so that private
// user data cannot leak through misuse of a non-privateBrowing WebView
// instance. Can't just null out mAwContents as we never null-check it
// before use.
destroy();
}
}
});
}

在第39行进行了 mWebSettings 的初始化,原来是 ContentSettingsAdapter。

5、 setMediaPlaybackRequiresUserGesture() 分析

经过以上我们队Google大神的膜拜,我们找到了mWebSettings,下面来看下 setMediaPlaybackRequiresUserGesture方法:


@Override
public void setMediaPlaybackRequiresUserGesture(boolean require) {
mAwSettings.setMediaPlaybackRequiresUserGesture(require);
}

好吧,又是调用的 mAwSettings 的 setMediaPlaybackRequiresUserGesture 方法,那 mAwSettings 是什么呢?


public ContentSettingsAdapter(AwSettings awSettings) {
mAwSettings = awSettings;
}

原来是在构造函数中注入的,回到第4小节的最后,这里 new 了一个AwSettings。


mWebSettings = new ContentSettingsAdapter(new AwSettings(
mWebView.getContext(), isAccessFromFileURLsGrantedByDefault,
areLegacyQuirksEnabled));

那么久来 AwSettings 中看下 setMediaPlaybackRequiresUserGesture 吧:

该类位于系统源码 external/​chromium_org/​android_webview/​java/​src/​org/​chromium/​android_webview/​AwSettings.java



public void setMediaPlaybackRequiresUserGesture(boolean require) {
synchronized (mAwSettingsLock) {
if (mMediaPlaybackRequiresUserGesture != require) {
mMediaPlaybackRequiresUserGesture = require;
mEventHandler.updateWebkitPreferencesLocked();
}
}
}

可以看到这里只是给一个变量 mMediaPlaybackRequiresUserGesture 设置了值,然后看到下面一个方法,豁然开朗:


@CalledByNative
private boolean getMediaPlaybackRequiresUserGestureLocked() {
return mMediaPlaybackRequiresUserGesture;
}

该方法是由JNI层调用的,external/​chromium_org/​android_webview/native/aw_settings.cc 中我们看到了:


web_prefs->user_gesture_required_for_media_playback =
Java_AwSettings_getMediaPlaybackRequiresUserGestureLocked(env, obj);

可见在内核中去调用该接口,判断是否允许音视频的自动播放。

以上所述是小编给大家介绍的关于Android HTML5 audio autoplay无效问题的解决方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程网网站的支持!

您可能感兴趣的文章:jquery html5 视频播放控制代码javascript实现简单的html5视频播放器详解HTML5 使用video标签实现选择摄像头功能transform实现HTML5 video标签视频比例拉伸实例详解Android编程使WebView支持HTML5 Video全屏播放的解决方法一个html5播放视频的video控件只支持android的默认格式mp4和3gp使用js检测浏览器是否支持html5中的video标签的方法HTML5视频播放标签video和音频播放标签audio标签的正确用法


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯