6

我是 Android 新手,一直试图让 HTML5 <audio> 标签在 WebView 浏览器中工作,但不断收到 MediaPlayer 错误 (1, -2147483648)。我要播放的文件位于“assets”目录下。我尝试在“res/raw”目录中引用一个文件,但结果相同。

为了验证可以找到和播放文件,作为我测试的一部分,我创建了一个代码变体,其中声音将通过 <a> 标签触发并由 WebViewClient 处理,使用此处的建议:

Android:使用 WebView 播放资产声音

它起作用了(尽管我不得不从 URL 中删除前导的“file:///android_asset”),但使用锚并不是我希望页面操作的方式。我希望在页面打开时播放背景声音,并在单击某些 <div> 标签时通过 Javascript 触发其他声音。我在其他地方读到 Android 现在支持标签,但我对它们没有运气,我正在使用最新的 SDK。

我创建了一个精简的测试页面来进行实验,其详细信息如下所示。我已经到处寻找没有运气的解决方案。我不知道缺少什么(我的偏好是尽可能避免使用任何附加组件并单独使用 Android)。

资产目录布局

assets
 > audio
   > a-00099954.mp3
   > a-00099954.ogg
 > image
 > script
 > style
 > video
 audioTest.html

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");
   }
}

显现

<?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>

网页

<!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"/>
        &#160;
     </audio>
   </div>
</body>
</html>
4

3 回答 3

6

我已经尝试了 3 种方法来解决这个问题,每种方法都是其他方法的变体,每种方法都涉及将原始音频文件从内部“资产”目录(默认 MediaPlayer 显然无法访问)复制到一个目录可以访问。各种目标目录在链接中描述:

http://developer.android.com/guide/topics/data/data-storage.html

其中使用了这三个:

  • 内部存储 [ /data/data/your.package.name/files ; IE。context.getFilesDir() ]
  • 外部应用程序存储 [ /mnt/sdcard/Android/data/package_name/files/Music ; IE。context.getExternalFilesDir(null)]
  • 外部通用存储 [ /mnt/sdcard/temp ; IE。Environment.getExternalStorageDirectory() + "/temp" ]

在所有情况下,文件都被分配了“世界可读”权限,以使默认 MediaPlayer 可以访问它们。第一种方法(内部)是首选,因为文件作为包本身的一部分并入,可以通过设备的“管理应用程序”应用程序从“清除数据”选项中删除,并在卸载应用程序时自动删除。第二种方法与第一种方法类似,但仅在卸载应用程序时删除文件,依赖于外部存储,并且需要权限才能写入清单中指定的外部存储。第三种方法是最不利的;它与第二种类似,但在卸载应用程序时没有清理音频文件。为每个标签提供了两个标签,

内部存储解决方案

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);
    }

    ...
}

对应的 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" />
        &#160;
     </audio>
   </div>

外部通用存储解决方案

使用此解决方案,尝试使用“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"/>

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()

对应的 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" />
        &#160;
     </audio>
   </div>

外部应用存储解决方案

我不会在这里列出代码,但它本质上是上面显示的解决方案的混合。请注意,Android 必须查看文件扩展名并决定将文件存储在何处;无论您指定目录“context.getExternalFilesDir(null)”还是“context.getExternalFilesDir(Environment.DIRECTORY_MUSIC)”,在这两种情况下,文件都将写入目录“/mnt/sdcard/Android/data/package_name/files/Music ”。

最后的评论

虽然上述方法有效,但也存在不足之处。首先,相同的数据被复制,因此存储空间翻倍。其次,相同的 MediaPlayer 用于所有声音文件,因此播放一个将中断其前身,从而阻止播放单独的前景音频的背景音频。当我尝试使用此处描述的解决方案时,我能够同时播放多个音频文件:

Android:使用 WebView 播放资产声音

我认为更好的解决方案是使用 Javascript 函数来启动音频,然后调用 Android Java 方法来启动它自己的 MediaPlayer(我最终可能会对此进行试验)。

于 2012-09-18T16:49:17.543 回答
1

在我的情况下,错误是由于媒体播放器对资产目录下本地存储的音频没有文件权限。尝试将音频存储到 /mnt/sdCARD 目录。

于 2012-09-10T06:24:40.083 回答
0

当心,蛮力方法

我假设您希望在网页内而不是直接播放音频。我不确定以下内容是否会有所帮助,但是当我不控制 HTML(即从某些第三方站点加载)时,我遇到了类似的情况,我仍然需要控制它在启动时的行为。例如,Vimeo 出现(此时)忽略即时版本的 WebView 中的“自动播放”uri 参数。因此,为了在页面加载时播放视频,我采用了获得专利的“将手臂插入到肘部”的方法。在您的情况下,隐藏标签(根据您上面引用的帖子),然后模拟点击的好处是在 WebView 完全加载之前不检测页面。然后,您注入 JS 代码(从这里采用:在 Android Webview 中,我可以修改网页吗?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");
            }
    }

使用直接 JS 或模拟点击,您可以检测您的页面。加载 jQuery 对于一个小标签位置似乎有点笨拙,所以也许您可以使用以下内容(从这里无耻地从如何在 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');";
    }

只要我已经告诉你令人尴尬的事情,我就会承认我使用 Chrome 在第三方页面中四处寻找我想要以这种方式检测的东西。由于 Android 上还没有开发人员工具,因此我调整了 UserAgent,此处描述为http://www.technipages.com/google-chrome-change-user-agent-string.html。然后我买了咖啡(液体,而不是 Rails Gem),并在其他人的代码中捣乱。

于 2012-09-07T09:45:25.517 回答