33

我一直在尝试将 .srt 文件用于定时文本源(仅在 android 4.1+ http://developer.android.com/about/versions/android-4.1.html#Multimedia中可用)。第一个问题与获取 .srt 文件的文件描述符有关(在资产文件夹中,您将如何将它捆绑到您的应用程序中?)。该文件会自动压缩,因此您甚至无法在不更改编译设置或进行自定义构建的情况下看到该文件。最简单的解决方案是将 .srt 文件重命名为 .jpg ,这样它就不会被压缩并且 openFD 方法仍然有效。我现在添加 TimedTextSource:

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

现在文件正确加载并使用 myMP.getTrackInfo() 获取曲目列表,可以看到添加定时文本源后,第 6 个曲目的类型为“3”,即定时文本曲目类型。我已经使用 selectTrack 来选择这个轨道,如谷歌文档中所说,但这样做之后,没有任何字幕出现在我的 TimedTextListener 上:

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

只触发一次(我在文件中有 20 个定时文本事件),但文本参数始终为空。我已经进行了搜索,但找不到使用 timeText 的单个工作代码示例,并且它没有出现在任何示例项目中,除了来自 google 的 api 文档之外,实际上没有任何文档,但据我所知,没有人发布过它的工作示例。我正在更新到 Android 4.2 的谷歌 Nexus 上测试这个

4

3 回答 3

30

我能够让它工作,因为它仍然是一个悬而未决的问题,我将在此处包含完整的解决方案。

虽然更改文件扩展名以防止压缩的想法很好,但我更喜欢将srt文件从资源复制到设备上的应用程序本地目录,但无论如何为了完整起见,这里是一个扩展列表,不会t被压缩。

“.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调用,String其中包含设备上字幕文件的完整路径,并MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP作为第二个参数调用

  4. 通过调用选择TimedText曲目player.selectTrack()并通过the index of timedTextType搜索TrackInfo[]返回来传递player.getTrackInfo()(我通常会找到它2

  5. 设置监听器,player.setOnTimedTextListener()然后开始播放媒体文件player.start()

这是完整的课程:

要运行这个确切的类,您将需要文件res/raw夹下的两个文件sub.srtvideo.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随着媒体文件的进展,它正在自动更改(即从字幕文件中读取)

定时文本示例

编辑:

这是示例项目的代码

于 2013-02-18T04:05:46.453 回答
13

编辑:我应该指出,在过去几年中,后 KitKat 版本的 Android 已成为大多数使用应用程序的 android 设备市场份额。下面的实现是试图桥接与旧设备的兼容性。此时,我建议使用 TimedText 框架(在 KitKat 中运行良好)或 android 发布的更新替代方案,因为自定义解决方案可能需要大量维护成本。


我花了 2 天时间查看 android 源代码,试图消除这个 TimedText 框架引起的所有错误。

我的建议是完全跳过它们的实施。它是不完整和不一致的。在早期的版本中,很多文本同步都是在原生媒体播放器中完成的,所以很容易出现状态错误。

我的替代方法是使用 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 回答
-1

要让它与 .mp3 文件一起使用,请调用 player.start(); 在声明一个新的媒体播放器之后和 addtimedtext 代码之前。在下面的行之后

MediaPlayer player = MediaPlayer.create(this, R.raw.video);
于 2013-09-22T02:25:51.527 回答