33

時間指定されたテキスト ソースに .srt ファイルを使用しようとしています (android 4.1+ http://developer.android.com/about/versions/android-4.1.html#Multimediaでのみ利用可能) 。最初の問題は、.srt ファイルのファイル記述子を取得することと関係があります (assets フォルダーで、他にどのようにアプリにバンドルしますか?)。ファイルは自動的に圧縮されるため、コンパイル設定を変更したり、カスタム ビルドを実行したりしない限り、ファイルを見ることさえできません。最も簡単な解決策は、.srt ファイルの名前を .jpg に変更して、圧縮されずに openFD メソッドが機能するようにすることでした。私は今 TimedTextSource を追加しています:

_myMP.addTimedTextSource(getAssets().openFd("captions.jpg").getFileDescriptor(),   MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);

ファイルが正しくロードされ、myMP.getTrackInfo() を使用してトラックのリストを取得すると、時間指定テキスト ソースを追加した後、6 番目のトラックのタイプが「3」であることがわかります。これは時間指定テキスト トラック タイプです。Google ドキュメントに記載されているように、selectTrack を使用してこのトラックを選択しましたが、その後、キャプションが表示されず、TimedTextListener に表示されません。

 _myMP.setOnTimedTextListener(new OnTimedTextListener(){
        @Override
        public void onTimedText(MediaPlayer mp, TimedText text) {
                if (text!=null)
                   Log.d("TimedText", text.getText());  
            }       
        });

一度だけ起動します (ファイルには 20 のタイミング テキスト イベントがあります) が、テキスト パラメータは常に null です。私は検索を行ったが、timeText を使用する単一の実用的なコード例を見つけることができず、どのサンプル プロジェクトにも表示されません。文字通り、Google の API ドキュメント以外のドキュメントはありませんが、私が知る限り、誰も投稿していませんまだそれの実例。Android 4.2に更新されたGoogle Nexusでこれをテストしています

4

3 に答える 3

30

これを機能させることができました。これはまだ未解決の問題であるため、ここに完全な解決策を含めます。

圧縮を防ぐためにファイル拡張子を変更するというアイデアは素晴らしいですが、私はsrtファイルをリソースからデバイスのアプリ ローカル ディレクトリにコピーすることを好みますが、とにかく完全を期すために、ここにコピーできない拡張子のリストを示します。圧縮されません。

".jpg"、".jpeg"、".png"、".gif"、".wav"、".mp2"、".mp3"、".ogg"、".aac"、".mpg"、 ".mpeg"、".mid"、".midi"、".smf"、".jet"、".rtttl"、".imy"、".xmf"、".mp4"、".m4a"、 ".m4v"、".3gp"、".3gpp"、".3g2"、".3gpp2"、".amr"、".awb"、".wma"、".wmv"

ソリューションの手順は簡単です。

  1. インスタンスを作成し、次のいずれかをMediaPlayer呼び出して準備します。MediaPlayer.create()player.setDataSource()player.prepare()

  2. 字幕ファイルが Android デバイスにまだ存在しない場合は、リソース フォルダーからデバイスにコピーします。

  3. デバイス上の字幕ファイルのフル パスを含むplayer.addTimedTextSource()最初の引数 a を使用して呼び出し、2 番目の引数として呼び出しますStringMediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP

  4. TimedText呼び出してトラックを選択し、返されたから検索してplayer.selectTrack()渡します(通常は見つかります)the index of timedTextTypeTrackInfo[]player.getTrackInfo()2

  5. でリスナーを設定しplayer.setOnTimedTextListener()、メディア ファイルの再生を開始するplayer.start()

完全なクラスは次のとおりです。

この正確なクラスを実行するには、res/rawフォルダーsub.srtの下に 2 つのファイルが必要ですvideo.mp4(または任意の拡張子)。TextView次に、 id でa を定義しますtxtDisplay。最後に、プロジェクト/デバイス/エミュレーターがサポートする必要がありますAPI 16

public class MainActivity extends Activity implements OnTimedTextListener {
    private static final String TAG = "TimedTextTest";
    private TextView txtDisplay;
    private static Handler handler = new Handler();

    @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    txtDisplay = (TextView) findViewById(R.id.txtDisplay);
    MediaPlayer player = MediaPlayer.create(this, R.raw.video);
    try {
        player.addTimedTextSource(getSubtitleFile(R.raw.sub),
                MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);
        int textTrackIndex = findTrackIndexFor(
                TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT, player.getTrackInfo());
        if (textTrackIndex >= 0) {
            player.selectTrack(textTrackIndex);
        } else {
            Log.w(TAG, "Cannot find text track!");
        }
        player.setOnTimedTextListener(this);
        player.start();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private int findTrackIndexFor(int mediaTrackType, TrackInfo[] trackInfo) {
    int index = -1;
    for (int i = 0; i < trackInfo.length; i++) {
        if (trackInfo[i].getTrackType() == mediaTrackType) {
            return i;
        }
    }
    return index;
}

private String getSubtitleFile(int resId) {
    String fileName = getResources().getResourceEntryName(resId);
    File subtitleFile = getFileStreamPath(fileName);
    if (subtitleFile.exists()) {
        Log.d(TAG, "Subtitle already exists");
        return subtitleFile.getAbsolutePath();
    }
    Log.d(TAG, "Subtitle does not exists, copy it from res/raw");

    // Copy the file from the res/raw folder to your app folder on the
    // device
    InputStream inputStream = null;
    OutputStream outputStream = null;
    try {
        inputStream = getResources().openRawResource(resId);
        outputStream = new FileOutputStream(subtitleFile, false);
        copyFile(inputStream, outputStream);
        return subtitleFile.getAbsolutePath();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        closeStreams(inputStream, outputStream);
    }
    return "";
}

private void copyFile(InputStream inputStream, OutputStream outputStream)
        throws IOException {
    final int BUFFER_SIZE = 1024;
    byte[] buffer = new byte[BUFFER_SIZE];
    int length = -1;
    while ((length = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, length);
    }
}

// A handy method I use to close all the streams
private void closeStreams(Closeable... closeables) {
    if (closeables != null) {
        for (Closeable stream : closeables) {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

@Override
public void onTimedText(final MediaPlayer mp, final TimedText text) {
    if (text != null) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                int seconds = mp.getCurrentPosition() / 1000;

                txtDisplay.setText("[" + secondsToDuration(seconds) + "] "
                        + text.getText());
            }
        });
    }
}

// To display the seconds in the duration format 00:00:00
public String secondsToDuration(int seconds) {
    return String.format("%02d:%02d:%02d", seconds / 3600,
            (seconds % 3600) / 60, (seconds % 60), Locale.US);
}
}

そして、ここsubtitleに私が例として使用しているファイルがあります:

1
00:00:00,220 --> 00:00:01,215
First Text Example

2
00:00:03,148 --> 00:00:05,053
Second Text Example

3
00:00:08,004 --> 00:00:09,884
Third Text Example

4
00:00:11,300 --> 00:00:12,900
Fourth Text Example

5
00:00:15,500 --> 00:00:16,700
Fifth Text Example

6
00:00:18,434 --> 00:00:20,434
Sixth Text Example

7
00:00:22,600 --> 00:00:23,700
Last Text Example

TextViewメディア ファイルが進行するにつれて が自動的に変化する (つまり、字幕ファイルからの読み取り) ことを示すテスト アプリのスクリーンショットを次に示します。

TimedText の例

編集:

サンプルプロジェクトのコードは次のとおりです

于 2013-02-18T04:05:46.453 に答える
13

編集: ここ数年で、Android のキットカット後のバージョンが、アプリを使用する Android デバイスの市場シェアのほとんどになったことを指摘しておく必要があります。以下の実装は、古いデバイスとの互換性を橋渡しする試みでした。この時点で、TimedText フレームワーク (KitKat で問題なく動作) を使用するか、Android によってリリースされた新しい代替手段を使用することをお勧めします。カスタム ソリューションにはかなりのメンテナンス コストがかかる可能性があるためです。


この TimedText フレームワークが引き起こしているすべてのバグを解決しようとして、Android ソースを 2 日間調べました。

私の推奨は、それらの実装を完全にスキップすることです。不完全で一貫性がありません。以前のバージョンでは、多くのテキスト同期がネイティブ メディア プレーヤーで行われるため、状態エラーが発生しがちです。

私の代替手段は、Textview サブクラスを使用することです。

package ca.yourpackage.yourapp;

import android.content.Context;
import android.media.MediaPlayer;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

/**
 * Created by MHDante on 2015-07-26.
 */
public class SubtitleView extends TextView implements Runnable{
    private static final String TAG = "SubtitleView";
    private static final boolean DEBUG = false;
    private static final int UPDATE_INTERVAL = 300;
    private MediaPlayer player;
    private TreeMap<Long, Line> track;

    public SubtitleView(Context context) {
        super(context);
    }


    public SubtitleView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public void run() {
        if (player !=null && track!= null){
            int seconds = player.getCurrentPosition() / 1000;
            setText((DEBUG?"[" + secondsToDuration(seconds) + "] ":"")
                    + getTimedText(player.getCurrentPosition()));
        }
        postDelayed(this, UPDATE_INTERVAL);
    }

    private String getTimedText(long currentPosition) {
        String result = "";
        for(Map.Entry<Long, Line> entry: track.entrySet()){
            if (currentPosition < entry.getKey()) break;
            if (currentPosition < entry.getValue().to) result = entry.getValue().text;
        }
        return result;
    }

    // To display the seconds in the duration format 00:00:00
    public String secondsToDuration(int seconds) {
        return String.format("%02d:%02d:%02d", seconds / 3600,
                (seconds % 3600) / 60, (seconds % 60), Locale.US);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        postDelayed(this, 300);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        removeCallbacks(this);
    }
    public void setPlayer(MediaPlayer player) {
        this.player = player;
    }

    public void setSubSource(int ResID, String mime){
        if(mime.equals(MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP))
            track = getSubtitleFile(ResID);
        else
            throw new UnsupportedOperationException("Parser only built for SRT subs");
    }

    /////////////Utility Methods:
    //Based on https://github.com/sannies/mp4parser/
    //Apache 2.0 Licence at: https://github.com/sannies/mp4parser/blob/master/LICENSE

    public static TreeMap<Long, Line> parse(InputStream is) throws IOException {
        LineNumberReader r = new LineNumberReader(new InputStreamReader(is, "UTF-8"));
        TreeMap<Long, Line> track = new TreeMap<>();
        while ((r.readLine()) != null) /*Read cue number*/{
            String timeString = r.readLine();
            String lineString = "";
            String s;
            while (!((s = r.readLine()) == null || s.trim().equals(""))) {
                lineString += s + "\n";
            }
            long startTime = parse(timeString.split("-->")[0]);
            long endTime = parse(timeString.split("-->")[1]);
            track.put(startTime, new Line(startTime, endTime, lineString));
        }
        return track;
    }

    private static long parse(String in) {
        long hours = Long.parseLong(in.split(":")[0].trim());
        long minutes = Long.parseLong(in.split(":")[1].trim());
        long seconds = Long.parseLong(in.split(":")[2].split(",")[0].trim());
        long millies = Long.parseLong(in.split(":")[2].split(",")[1].trim());

        return hours * 60 * 60 * 1000 + minutes * 60 * 1000 + seconds * 1000 + millies;

    }

    private TreeMap<Long, Line> getSubtitleFile(int resId) {
        InputStream inputStream = null;
        try {
            inputStream = getResources().openRawResource(resId);
            return parse(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static class Line {
        long from;
        long to;
        String text;


        public Line(long from, long to, String text) {
            this.from = from;
            this.to = to;
            this.text = text;
        }
    }
}

使用法:

//I used and reccomend asyncPrepare()
MediaPlayer mp = MediaPlayer.create(context, R.raw.video);
SubtitleView subView = (SubtitleView) getViewbyId(R.id.subs_box);
subView.setPlayer(mp);
subView.setSubSource(R.raw.subs_intro, MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);

レイアウト xml ファイルで、字幕を表示するように textView を作成し、クラスを ca.yourpagckage.yourapp.SubtitleView に変更します。

<ca.yourpagckage.yourapp.SubtitleView
    android:layout_width="300dp"
    android:layout_height="300dp"
    android:text="Subtitles go Here"
    android:id="@+id/subs_box"/>

幸運を。

于 2015-07-27T02:52:37.907 に答える