簡體   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

我是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>

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>

我已經嘗試了3種解決這個問題的方法,每個方法都是其他方法的變體,每個方法都涉及將原始音頻文件從內部“assets”目錄(顯然是默認的MediaPlayer無法訪問)復制到一個目錄可以訪問。 鏈接描述了各種目標目錄:

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

其中三個被使用:

  • 內部存儲[ /data/data/your.package.name/files ; 即。 context.getFilesDir()]
  • 外部應用程序存儲[ / mnt / sdcard / Android / data / package_name / files / Music ; 即。 context.getExternalFilesDir(null)]
  • 外部一般存儲[ / mnt / sdcard / temp ; 即。 Environment.getExternalStorageDirectory()+“/ temp”]

在所有情況下,文件都被分配了“世界可讀”權限,以使其可供默認MediaPlayer訪問。 第一種方法(內部)是首選方法,因為文件作為包本身的一部分合並,可以通過設備的“管理應用程序”應用程序從“清除數據”選項中刪除,並在卸載應用程序時自動刪除。 第二種方法與第一種方法類似,但文件僅在卸載應用程序時被刪除,依賴於外部存儲,並且需要特權才能寫入清單中指定的外部存儲。 第三種方法是最不利的; 它類似於第二個,但是在卸載應用程序時沒有清除音頻文件。 為每個標記提供了兩個標記,第一個使用Android路徑,第二個使用原始路徑,以便在合並到Android應用程序之前,可以通過Chrome瀏覽器檢查HTML文件。

內部存儲解決方案

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函數來啟動音頻,然后調用啟動自己的MediaPlayer的Android Java方法(我最終可能會嘗試使用它)。

在我的情況下,錯誤是由於媒體播放器沒有資產目錄下本地存儲的音頻的文件權限。 嘗試將音頻存儲到/ mnt / sdCARD目錄中。

當心,蠻力方法

我假設您希望在網頁內播放音頻而不是直接播放。 我不確定以下內容是否會有所幫助,但是當我不控制HTML(即從某個第三方網站加載)時,我遇到了類似的情況,我仍然需要在啟動時控制其行為。 例如,Vimeo出現(此時)忽略WebView即時版本中的“autoplay”uri參數。 因此,為了在頁面加載時播放視頻,我使用了專利的“插入臂,大致達到肘部”的方法。 在您的情況下,隱藏標記(根據您在上面引用的帖子),然后模擬單擊具有在WebView完全加載之前不檢測頁面的好處。 然后,你注入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");
            }
    }

使用直接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寶石),並在其他人的代碼中戳戳導致惡作劇。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM