Vic*_*orn 5 audio android assets webview media-player
我是Android的新手,并且一直试图让HTML5 <audio>标签在WebView浏览器中运行,但不断获得MediaPlayer错误(1,-2147483648).我正在尝试播放的文件位于"assets"目录下.我尝试在"res/raw"目录中引用一个文件,但结果相同.
为了验证文件是否可以找到和播放,作为我的测试的一部分,我创建了一个代码的变体,声音将通过<a>标签触发,并由WebViewClient处理,使用以下建议:
它工作(虽然我不得不从URL中删除领先的"file:/// android_asset"),但使用锚点并不是我希望页面运行的方式.我希望在页面打开时播放背景声音,并在点击某些<div>标签时通过Javascript触发其他声音.我在其他地方读到Android现在支持标签,但我没有运气,我正在使用最新的SDK.
我已经创建了一个精简的测试页面进行试验,其详细信息如下所示.我一直在找一个没有运气的解决方案.我不知道缺少什么(我的偏好是尽可能避免使用任何附加组件并且仅使用Android).
资产目录布局
assets
> audio
> a-00099954.mp3
> a-00099954.ogg
> image
> script
> style
> video
audioTest.html
Run Code Online (Sandbox Code Playgroud)
Java代码
package com.test.audiotag;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
public class MainActivity extends Activity
{
private WebView localBrowser;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
localBrowser = (WebView)findViewById(R.id.localbrowser);
localBrowser.getSettings().setJavaScriptEnabled(true);
localBrowser.loadUrl("file:///android_asset/audioTest.html");
}
}
Run Code Online (Sandbox Code Playgroud)
表现
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android ="http://schemas.android.com/apk/res/android"
package ="com.test.audiotag"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="14" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name =".MainActivity"
android:label ="@string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Run Code Online (Sandbox Code Playgroud)
HTML页面
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=300, height=400">
<style type="text/css">
#centre_all
{
-webkit-box-flex : 0;
position : relative;
background-color : #D0D000;
border : 2px dotted red;
-webkit-box-shadow: 0 0 5px red;
}
</style>
</head>
<body>
<div id="centre_all" style="width:300px;height:400px;">
<audio controls="controls" autoplay="autoplay">
<source src="audio/a-00099954.mp3" type="audio/mpeg" />
<source src="audio/a-00099954.ogg" type="audio/ogg"/>
 
</audio>
</div>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)
我已经尝试了3种解决这个问题的方法,每个方法都是其他方法的变体,每个方法都涉及将原始音频文件从内部"assets"目录(显然是默认的MediaPlayer无法访问)复制到一个目录可以访问.链接描述了各种目标目录:
http://developer.android.com/guide/topics/data/data-storage.html
其中三个被使用:
在所有情况下,文件都被分配了"世界可读"权限,以使其可供默认MediaPlayer访问.第一种方法(内部)是首选方法,因为文件作为包本身的一部分合并,可以通过设备的"管理应用程序"应用程序从"清除数据"选项中删除,并在卸载应用程序时自动删除.第二种方法是类似于第一,但文件只除去当应用程序被卸载时,是依赖于外部存储,并且需要权限写入清单中指定的外部存储.第三种方法是最不利的; 它类似于第二个,但是在卸载应用程序时没有清除音频文件.两个标签提供了每个标签,第一个使用Android的路径,第二个使用原始路径,使这个HTML文件可以通过Chrome浏览器在Android应用程序被整合前进行检查.
内部存储解决方案
Java代码
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
localBrowser = (WebView)findViewById(R.id.localbrowser);
localBrowser.getSettings().setJavaScriptEnabled(true);
localBrowser.setWebViewClient(new WebViewClient());
localBrowser.setWebChromeClient(new WebChromeClient());
...
Context context = localBrowser.getContext();
File filesDir = context.getFilesDir();
Log.d("FILE PATH", filesDir.getAbsolutePath());
if (filesDir.exists())
{
filesDir.setReadable(true, false);
try
{
String[] audioFiles = context.getAssets().list("audio");
if (audioFiles != null)
{
byte[] buffer;
int length;
InputStream inStream;
FileOutputStream outStream;
for (int i=0; i<audioFiles.length; i++)
{
inStream = context.getAssets().open(
"audio/" + audioFiles[i] );
outStream = context.openFileOutput(audioFiles[i],
Context.MODE_WORLD_READABLE);
buffer = new byte[8192];
while ((length=inStream.read(buffer)) > 0)
{
outStream.write(buffer, 0, length);
}
// Close the streams
inStream.close();
outStream.flush();
outStream.close();
}
}
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Feedback
String[] fileList = context().fileList();
Log.d("FILE LIST", "--------------");
for (String fileName : fileList)
{
Log.d("- FILE", fileName);
}
...
}
Run Code Online (Sandbox Code Playgroud)
相应的HTML标签
<div id="centre_all" style="width:300px;height:400px;">
<audio controls="controls" autoplay="autoplay">
<source src="/data/data/com.test.audiotag/files/a-00099954.mp3" type="audio/mpeg" />
<source src="audio/a-00099954.mp3" type="audio/mpeg" />
 
</audio>
</div>
Run Code Online (Sandbox Code Playgroud)
外部通用存储解决方案
使用此解决方案,尝试使用"onDestroy()"方法清除复制到"/ mnt/sdcard/temp"目录的临时文件,但在实践中,该方法似乎永远不会执行(尝试"onStop()"和" onPause()"被调用,但他们过早地删除了文件".这里给出的"onDestroy()"的讨论证实它的代码可能不会执行:
http://developer.android.com/reference/android/app/Activity.html#onDestroy()
清单输入
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Run Code Online (Sandbox Code Playgroud)
Java代码
private String[] audioList;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
localBrowser = (WebView)findViewById(R.id.localbrowser);
localBrowser.getSettings().setJavaScriptEnabled(true);
localBrowser.setWebViewClient(new WebViewClient());
localBrowser.setWebChromeClient(new WebChromeClient());
...
Context context = localBrowser.getContext();
File dirRoot = new File(Environment.getExternalStorageDirectory().toString());
Log.d("ROOT DIR PATH", dirRoot.getAbsolutePath());
if (dirRoot.exists())
{
File dirTemp = new File(dirRoot.getAbsolutePath() + "/temp");
if (!dirTemp.exists())
{
if (!dirTemp.mkdir())
{
Log.e("AUDIO DIR PATH", "FAILED TO CREATE " +
dirTemp.getAbsolutePath());
}
}
else
{
Log.d("AUDIO DIR PATH", dirTemp.getAbsolutePath());
}
if (dirTemp.exists())
{
dirTemp.setReadable(true, false);
dirTemp.setWritable(true);
try
{
String[] audioFiles = context.getAssets().list("audio");
if (audioFiles != null)
{
byte[] buffer;
int length;
InputStream inStream;
FileOutputStream outStream;
audioList = new String[audioFiles.length];
for (int i=0; i<audioFiles.length; i++)
{
inStream = context.getAssets().open(
"audio/" + audioFiles[i] );
audioList[i] = dirTemp.getAbsolutePath() + "/" +
audioFiles[i];
outStream = new FileOutputStream(audioList[i]);
buffer = new byte[8192];
while ( (length=inStream.read(buffer)) > 0)
{
outStream.write(buffer, 0, length);
}
// Close the streams
inStream.close();
outStream.flush();
outStream.close();
//audioFile = new File(audioList[i]);
//audioFile.deleteOnExit();
}
}
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Feedback
String[] fileList = dirTemp.list();
Log.d("FILE LIST", "--------------");
for (String fileName : fileList)
{
Log.d("- FILE", fileName);
}
}
...
} // onCreate()
@Override
public void onDestroy()
{
for (String audioFile : audioList)
{
File hFile = new File(audioFile);
if (hFile.delete())
{
Log.d("DELETE FILE", audioFile);
}
else
{
Log.d("DELETE FILE FAILED", audioFile);
}
}
super.onDestroy();
} // onDestroy()
Run Code Online (Sandbox Code Playgroud)
相应的HTML标签
<div id="centre_all" style="width:300px;height:400px;">
<audio controls="controls" autoplay="autoplay">
<source src="/mnt/sdcard/temp/a-00099954.mp3" type="audio/mpeg" />
<source src="audio/a-00099954.mp3" type="audio/mpeg" />
 
</audio>
</div>
Run Code Online (Sandbox Code Playgroud)
外部应用存储解决方案
我不会在这里列出代码,但它基本上是上面显示的解决方案的混合.请注意,Android必须查看文件扩展名并确定文件的存储位置; 无论你指定目录"context.getExternalFilesDir(null)"还是"context.getExternalFilesDir(Environment.DIRECTORY_MUSIC)",在这两种情况下,文件都将被写入目录"/ mnt/sdcard/Android/data/package_name/files/Music ".
最后评论
虽然上述方法有效,但存在缺点.首先,重复相同的数据,因此存储空间加倍.其次,相同的MediaPlayer用于所有声音文件,因此播放一个将中断其前一个,这将阻止能够播放播放单独前景音频的背景音频.当我尝试这里描述的解决方案时,我能够同时播放多个音频文件:
我认为一个更好的解决方案将是一个利用JavaScript函数来启动这反过来调用启动它自己的MediaPlayer(我可能最终与实验)一个Android的Java方法的音频.
当心,暴力方法
我假设您希望在网页内播放音频而不是直接播放。我不确定以下内容是否会有帮助,但当我不控制 HTML(即从某些第三方站点加载)时,我遇到过类似的情况,并且我仍然需要在启动时控制其行为。例如,Vimeo(此时)似乎忽略即时版本 WebView 中的“autoplay”uri 参数。因此,为了在页面加载时播放视频,我采用了获得专利的“将手臂插入大约肘部”的方法。在您的情况下,隐藏标签(根据您上面引用的帖子),然后模拟点击的好处是在 WebView 完全加载之前不会检测页面。然后,注入 JS 代码(这是从这里采用的:In Android Webview, am I able to edit apages's DOM?):
mWebView.setWebViewClient(new CustomVimeoViewClient(){
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
String injection = injectPageMonitor();
if(injection != null) {
Log.d(TAG, " Injecting . . .");
view.loadUrl(injection);
}
Log.d(TAG, "Page is loaded");
}
}
Run Code Online (Sandbox Code Playgroud)
使用直接 JS 或模拟点击,您可以检测您的页面。对于一个小标签位置来说,加载 jQuery 似乎有点笨拙,所以也许您可以使用以下内容(这是从这里无耻地摘自How to Get Element By Class in JavaScript?):
private String injectPageMonitor() {
return( "javascript:" +
"function eventFire(el, etype){ if (el.fireEvent) { (el.fireEvent('on' + etype)); } else { var evObj = document.createEvent('Events'); evObj.initEvent(etype, true, false); el.dispatchEvent(evObj); }}" +
"eventFire(document.querySelectorAll('.some_class_or_another')[0], 'click');";
}
Run Code Online (Sandbox Code Playgroud)
只要我已经告诉你一些令人尴尬的事情,我就会承认我使用 Chrome 在第三方页面中查找我想要以这种方式检测的东西。由于 Android 上还没有开发人员工具,因此我调整了 UserAgent,如下所述:http://www.technipages.com/google-chrome-change-user-agent-string.html。然后我购买了咖啡(液体,而不是 Rails Gem),并在其他人的代码中乱搞,造成了恶作剧。
| 归档时间: |
|
| 查看次数: |
7386 次 |
| 最近记录: |