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

前言:在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的配置

/** * Gets the WebSettings object used to control the settings for this * WebView. * * @return a WebSettings object that can be used to control this WebView's * settings */ public WebSettings getSettings() { checkThread(); return mProvider.getSettings(); }

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

2、 mPeovider是在哪初始化的?

/** * @hide */ @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

/** * See {@link android.webkit.WebSettings#setMediaPlaybackRequiresUserGesture}. */ 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无效问题的解决方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

时间: 2024-10-22 22:20:18

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

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

前言:在android HTML5 开发中有不少人遇到过 audio 标签 autoplay在某些设备上无效的问题,网上大多是讲怎么在js中操作,即在特定的时刻调用audio的play()方法,在android上还是无效. 一.解决方案 在android 4.2添加了允许用户手势触发音视频播放接口,该接口默认为 true ,即默认不允许自动播放音视频,只能是用户交互的方式由用户自己促发播放. WebView webView = this.finishActivity(R.id.main_act_

HTML5 Audio标签方法和函数API介绍

 问说网 > 文章教程 > 网页制作 > HTML5 Audio标签方法和函数API介绍 Audio APIHTML5HTML5 Audio预加载 HTML5 Audio标签方法和函数API介绍 问说网 • 2014-06-19 09:53:52 • 3561 浏览 文章目录 audio常用属性 audio音乐格式的支持 audio属性 参数说明 最近想弄一个类似在线播放MP3的应用,刚开始想用flash播放器,但是已经很就没有弄flash了,用起来很老火,于是想到了HTML5的audi

HTML5 audio标签使用js进行播放控制实例

HTML5 audio标签使用js进行播放控制实例  <audio>标签可以在HTML5浏览器中播放音频文件. <audio>默认提供一个控制面板,但是有些时候我们只需要播放声音,控制面板由我们自己来定义其显示的状态. 这里我们可以使用JS来进行控制,代码如下:   代码如下: var audio ; window.onload = function(){ initAudio(); } var initAudio = function(){ //audio = document.c

Android+Html5混合开发仿微信朋友圈_Android

开发之前大约从去年开始吧, 也可能是前年 Html5好像火得不得了, 不得了...总能从网上听说到 XXX混合开发, 为了紧跟潮流(虽然有点儿晚了), 咱们也看看Android+Html5混合开发是怎样的! 今天带来的案例是微信的朋友圈, 因为我觉得是微信把H5给"捧红了". 不过丑话说在前头, 咱们的仿朋友圈可是"低仿", 只是把混合开发的大致流程说说, 界面可能不堪入目...见谅.. 开发环境Android Studio 2.2.2 JDK1.7 API 24

android 蓝牙开发startDiscovery()无效

问题描述 android 蓝牙开发startDiscovery()无效 这是BroadcastReceiver 找到设备的时候添加到列表里,照完后显示AlertDialog就没截图 这是注册广播 这是开始搜索的 弄个progressdialog简单看下进度 用这个的时候完全搜索不到设备,去设置里面手动弄就会发现可以搜索到,为什么?手机都是android 4.3以上的 解决方案 目测你用的是蓝牙2.0的搜索功能,但实际想用4.0 的功能?如果不是,请追问:如果是,参看http://blog.csd

开大你的音响,感受HTML5 Audio API带来的视听盛宴

话说HTML5的炫酷真的是让我爱不释手,即使在这个提到IE就伤心不完的年代.但话又说回来,追求卓越Web创造更美世界这样高的追求什么时候又与IE沾过边儿呢?所以当你在看本文并且我们开始讨论HTML5等前沿东西的时候,我们默认是把IE排除在外的.本文的例子可以工作在最新的Chrome及Firefox浏览器下,其他浏览器暂未测试. 若下方未出现演示页面请刷新.  你也可以点此全屏演示  或者前往GitHub进行代码下载然后本地运行. 你还可以 下载示例音乐(如果你手头没有音频文件的话) 文件列表:b

Three.js + HTML5 Audio API 打造3D音乐频谱,Let’s ROCK!

继续玩味之前写的音乐频谱作品,将原来在Canvas标签上的 作图利用Three.js让它通过WebGL呈现,这样就打造出了一个全立体感的频谱效果了. 项目详情及源码 项目GitHub地址:https://github.com/Wayou/3D_Audio_Spectrum_VIsualizer/tree/master 在线演示地址:http://wayou.github.io/3D_Audio_Spectrum_VIsualizer 如果你想的话,可以从这里下载示例音乐:http://pan.b

Android+Html5混合开发仿微信朋友圈

开发之前 大约从去年开始吧, 也可能是前年 Html5好像火得不得了, 不得了...总能从网上听说到 XXX混合开发, 为了紧跟潮流(虽然有点儿晚了), 咱们也看看Android+Html5混合开发是怎样的! 今天带来的案例是微信的朋友圈, 因为我觉得是微信把H5给"捧红了". 不过丑话说在前头, 咱们的仿朋友圈可是"低仿", 只是把混合开发的大致流程说说, 界面可能不堪入目...见谅.. 开发环境 Android Studio 2.2.2 JDK1.7 API 2

兼容-ie8下canvas.getContext()方法无效,求解决方案

问题描述 ie8下canvas.getContext()方法无效,求解决方案 canvas.getContext()是绘制一个绘画环境,目前只支持2d属性 网上也看过excanvas.js的方法,但是报错 //未知的运行时错误--excanvas.js 行144,字符9: el.innerHTML = ''; //意外地调用了方法或属性访问.--excanvas.js 行311,字符5: surfaceElement.appendChild(el); 解决方案 这个好像本来就不支持 解决方案二: