简体   繁体   English

Android 4.0.4 WebView MediaPlayer错误(1,-2147483648)使用 <audio> 标签和本地资产文件

[英]Android 4.0.4 WebView MediaPlayer Error (1, -2147483648) Using <audio> Tag and Local Assets File

I'm new to Android and have been trying to get the HTML5 <audio> tag to work in a WebView browser but keep getting MediaPlayer Error (1, -2147483648). 我是Android的新手,并且一直试图让HTML5 <audio>标签在WebView浏览器中运行,但不断获得MediaPlayer错误(1,-2147483648)。 The file I'm trying to play resides below the "assets" directory. 我正在尝试播放的文件位于“assets”目录下。 I've tried referencing a file in the "res/raw" directory, but with the same result. 我尝试在“res / raw”目录中引用一个文件,但结果相同。

To verify that the files could be found and played, as part of my tests I created a variation of the code where the sound would be triggered through an <a> tag and would be processed by a WebViewClient, using the suggestions here: 为了验证文件是否可以找到和播放,作为我的测试的一部分,我创建了一个代码的变体,声音将通过<a>标签触发,并由WebViewClient处理,使用以下建议:

Android: Playing an Asset Sound Using WebView Android:使用WebView播放资产声音

It worked (although I had to trim off the leading "file:///android_asset" from the URL), but using anchors is not how I want the pages to operate. 它工作(虽然我不得不从URL中删除领先的“file:/// android_asset”),但使用锚点并不是我希望页面运行的方式。 I'd like a background sound to play when the page opens and other sounds to be triggered through Javascript when certain <div> tags are clicked. 我希望在页面打开时播放背景声音,并在点击某些<div>标签时通过Javascript触发其他声音。 I've read elsewhere that Android now supports tags, but I've had no luck with them and I'm using the latest SDK. 我在其他地方读到Android现在支持标签,但我没有运气,我正在使用最新的SDK。

I've created a stripped down test page to experiment with, details of which are shown below. 我已经创建了一个精简的测试页面进行试验,其详细信息如下所示。 I've looked all over for a solution with no luck. 我一直在找一个没有运气的解决方案。 I don't know what's missing (my preference is to avoid any add-ons if possible and work with Android alone). 我不知道缺少什么(我的偏好是尽可能避免使用任何附加组件并且仅使用Android)。

Assets Directory Layout 资产目录布局

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

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

Manifest 表现

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

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

I've experimented with 3 approaches to addressing this problem, each of which is a variation of the others, and each involve copying the original audio files from the internal "assets" directory (apparently not accessible to the default MediaPlayer) to a directory which can be accessed. 我已经尝试了3种解决这个问题的方法,每个方法都是其他方法的变体,每个方法都涉及将原始音频文件从内部“assets”目录(显然是默认的MediaPlayer无法访问)复制到一个目录可以访问。 The various target directories are described at link: 链接描述了各种目标目录:

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

of which these three were used: 其中三个被使用:

  • internal storage [ /data/data/your.package.name/files ; 内部存储[ /data/data/your.package.name/files ; ie. 即。 context.getFilesDir() ] context.getFilesDir()]
  • external application storage [ /mnt/sdcard/Android/data/package_name/files/Music ; 外部应用程序存储[ / mnt / sdcard / Android / data / package_name / files / Music ; ie. 即。 context.getExternalFilesDir(null) ] context.getExternalFilesDir(null)]
  • external general storage [ /mnt/sdcard/temp ; 外部一般存储[ / mnt / sdcard / temp ; ie. 即。 Environment.getExternalStorageDirectory() + "/temp" ] Environment.getExternalStorageDirectory()+“/ temp”]

In all cases, the files were assigned "world readable" privileges to make them accessible to the default MediaPlayer. 在所有情况下,文件都被分配了“世界可读”权限,以使其可供默认MediaPlayer访问。 The first approach (internal) is preferred because the files are incorporated as part of the package itself and can be removed from the "Clear data" option through the device's "Manage Apps" application and are automatically removed when the application is uninstalled. 第一种方法(内部)是首选方法,因为文件作为包本身的一部分合并,可以通过设备的“管理应用程序”应用程序从“清除数据”选项中删除,并在卸载应用程序时自动删除。 The second approach is similar to the first, but the files are only removed when the application is uninstalled, is dependent on external storage, and requires privileges to write to external storage specified in the manifest. 第二种方法与第一种方法类似,但文件仅在卸载应用程序时被删除,依赖于外部存储,并且需要特权才能写入清单中指定的外部存储。 The third approach is the least favourable; 第三种方法是最不利的; it is similar to the second, but there is no cleanup of the audio files when the application is uninstalled. 它类似于第二个,但是在卸载应用程序时没有清除音频文件。 Two tags were provided for each tag, the first uses the Android path, and the second uses the original path so that the HTML files can be examined through a Chrome browser before being incorporated in the Android App. 为每个标记提供了两个标记,第一个使用Android路径,第二个使用原始路径,以便在合并到Android应用程序之前,可以通过Chrome浏览器检查HTML文件。

Internal Storage Solution 内部存储解决方案

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

    ...
}

Corresponding HTML Tags 相应的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>

External General Storage Solution 外部通用存储解决方案

With this solution, attempted to use "onDestroy()" method to cleanup temporary files copied to "/mnt/sdcard/temp" directory when done but in practise, the method never seemed to executed (tried both "onStop()" and "onPause()" which were called, but they prematurely removed the files). 使用此解决方案,尝试使用“onDestroy()”方法清除复制到“/ mnt / sdcard / temp”目录的临时文件,但在实践中,该方法似乎永远不会执行(尝试“onStop()”和“ onPause()“被调用,但他们过早地删除了文件”。 A discussion of "onDestroy()" given here confirms that it's code may not execute: 这里给出的“onDestroy()”的讨论证实它的代码可能不会执行:

http://developer.android.com/reference/android/app/Activity.html#onDestroy() http://developer.android.com/reference/android/app/Activity.html#onDestroy()

Manifest Entry 清单输入

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

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

Corresponding HTML Tags 相应的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>

External Application Storage Solution 外部应用存储解决方案

I won't list the code here, but it's essentially a blend of the solutions shown above. 我不会在这里列出代码,但它基本上是上面显示的解决方案的混合。 Note that Android must look at the file extensions and decide where to store the files; 请注意,Android必须查看文件扩展名并确定文件的存储位置; whether you specify directory "context.getExternalFilesDir(null)" or "context.getExternalFilesDir(Environment.DIRECTORY_MUSIC)", in both cases, the files will be written to directory "/mnt/sdcard/Android/data/package_name/files/Music". 无论你指定目录“context.getExternalFilesDir(null)”还是“context.getExternalFilesDir(Environment.DIRECTORY_MUSIC)”,在这两种情况下,文件都将被写入目录“/ mnt / sdcard / Android / data / package_name / files / Music ”。

Final Comments 最后评论

While the above approach works, there are shortcomings. 虽然上述方法有效,但存在缺点。 First, the same data is duplicated and therefore doubles the storage space. 首先,重复相同的数据,因此存储空间加倍。 Second, the same MediaPlayer is used for all sound files so the playing of one will interrupt its predecessor which prevents being able to play background audio over which separate foreground audio is played. 其次,相同的MediaPlayer用于所有声音文件,因此播放一个将中断其前一个,这将阻止能够播放播放单独前景音频的背景音频。 When I experimented with the solution described here, I was able to play multiple audio files simultaneously: 当我尝试这里描述的解决方案时,我能够同时播放多个音频文件:

Android: Playing an Asset Sound Using WebView Android:使用WebView播放资产声音

I think a better solution would be one that uses a Javascript function to launch the audio that in turn calls an Android Java method that starts its own MediaPlayer (I may eventually experiment with that). 我认为更好的解决方案是使用Javascript函数来启动音频,然后调用启动自己的MediaPlayer的Android Java方法(我最终可能会尝试使用它)。

In my case the error was due to the mediaplayer not having file permissions on the locally stored audio under assets directory. 在我的情况下,错误是由于媒体播放器没有资产目录下本地存储的音频的文件权限。 Try storing the audio onto the /mnt/sdCARD directory. 尝试将音频存储到/ mnt / sdCARD目录中。

Beware, Brute Force Approach 当心,蛮力方法

I'm assuming that you want the audio played inside a web page instead of directly. 我假设您希望在网页内播放音频而不是直接播放。 I am not sure the following will help, but I have hit similar situations when I do not control the HTML (ie loaded from some third party site), and I still need to control its behavior on launch. 我不确定以下内容是否会有所帮助,但是当我不控制HTML(即从某个第三方网站加载)时,我遇到了类似的情况,我仍然需要在启动时控制其行为。 For example, Vimeo appears (at this point) to ignore the "autoplay" uri parameter in the instant version of WebView. 例如,Vimeo出现(此时)忽略WebView即时版本中的“autoplay”uri参数。 So, to get a video to play when the page loads I employed the patented 'insert arms approximately up to the elbows' approach. 因此,为了在页面加载时播放视频,我使用了专利的“插入臂,大致达到肘部”的方法。 In your case, hiding a tag (per the post you cite above), and then simulating a click has the benefit of not instrumenting the page before the WebView has completely loaded. 在您的情况下,隐藏标记(根据您在上面引用的帖子),然后模拟单击具有在WebView完全加载之前不检测页面的好处。 Then, you inject JS code (Which is adopted from here: In Android Webview, am I able to modify a webpage's DOM? ): 然后,你注入JS代码(从这里采用: 在Android Webview中,我能修改网页的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");
            }
    }

Using either direct JS, or else simulating clicks, you can instrument your page. 使用直接JS或模拟点击,您可以检测页面。 Loading jQuery seems a bit ham-fisted for just one little tag location, so perhaps you might use the following (Which is shamelessly lifted from here How to Get Element By Class in JavaScript? ): 加载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');";
    }

And as long as I am already telling you embarrassing things, I will round out by confessing that I use Chrome to poke around in third-party pages to find things I want to instrument in this way. 只要我已经告诉你令人尴尬的事情,我将承认我使用Chrome浏览器在第三方页面中寻找我想用这种方式设置的东西。 Since there are no developer tools on Android (yet), I adjust the UserAgent, described here http://www.technipages.com/google-chrome-change-user-agent-string.html . 由于Android上还没有开发人员工具,我调整了UserAgent,这里描述了http://www.technipages.com/google-chrome-change-user-agent-string.html I then procure coffee (the liquid, not the Rails Gem), and poke about in other people's code causing mischief. 然后,我采购咖啡(液体,而不是Rails宝石),并在其他人的代码中戳戳导致恶作剧。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM