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

≡放荡痞女 提交于 2021-02-04 17:52:49

问题


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). The file I'm trying to play resides below the "assets" directory. I've tried referencing a file in the "res/raw" directory, but with the same result.

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:

Android: Playing an Asset Sound Using 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. 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. I've read elsewhere that Android now supports tags, but I've had no luck with them and I'm using the latest 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).

Assets Directory Layout

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

Java Code

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

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

回答1:


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. The various target directories are described at link:

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

of which these three were used:

  • internal storage [ /data/data/your.package.name/files; ie. context.getFilesDir() ]
  • external application storage [ /mnt/sdcard/Android/data/package_name/files/Music; ie. context.getExternalFilesDir(null) ]
  • external general storage [ /mnt/sdcard/temp; ie. Environment.getExternalStorageDirectory() + "/temp" ]

In all cases, the files were assigned "world readable" privileges to make them accessible to the default 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.

Internal Storage Solution

Java Code

@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

   <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). A discussion of "onDestroy()" given here confirms that it's code may not execute:

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

Manifest Entry

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

Java Code

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

   <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; 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".

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. When I experimented with the solution described here, I was able to play multiple audio files simultaneously:

Android: Playing an Asset Sound Using 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).




回答2:


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.




回答3:


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 (i.e. loaded from some third party site), and I still need to control its behavior on launch. For example, Vimeo appears (at this point) to ignore the "autoplay" uri parameter in the instant version of WebView. 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. Then, you inject JS code (Which is adopted from here: In Android Webview, am I able to modify a webpage'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");
            }
    }

Using either direct JS, or else simulating clicks, you can instrument your page. 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?):

    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. 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. I then procure coffee (the liquid, not the Rails Gem), and poke about in other people's code causing mischief.



来源:https://stackoverflow.com/questions/12290601/android-4-0-4-webview-mediaplayer-error-1-2147483648-using-audio-tag-and-l

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!