Android的MediaPlayer有个OnTimedText接口,可用用来实现外挂字幕的功能。这个接口目前只支持srt类型字幕,使用时,通过:

addTimedTextSource(String path, String mimeType)

添加外挂字幕源文件,通过:

setOnTimedTextListener(MediaPlayer.OnTimedTextListener listener)

注册MediaPlayer使用的Listener,重写Listener的onTimedText方法,MediaPlayer会自动触发回调,通知显示/隐藏字幕。但这个机制有个问题是:一旦进行的快退操作,显示过的字幕就不再显示了。而且,个别字幕和媒体文件时间轴不一致的情况下,也无法调整同步,因此放弃了这种做法。
为了实现外挂字幕,需要解决几个问题:
- 外挂字幕的解析;
- 外挂字幕的同步;
- 外挂字幕的显示;
下面简单介绍一下思路和关键代码。

1. 外挂字幕的解析

srt的语法比较简单,分为几个元素:
- 序号
- 时间
- 一行或多行文本
- 空行
这个空行很重要,可以简单判断出本节字幕的结束。
为了做字幕的解析,单独定义了一个类(因为想在类中定义一个Interface),类名叫:AddOnSubtitle,srt的解析直接在构造函数中进行了,解析过程中,使用了正则匹配输入是否合法:

...
    private final class Subtitle {
        int index;
        int beginTime;
        int endTime;
        String text;
    }

    private Vector<Subtitle> mSrtContainer = new Vector<>();

String _regTime = "\\d\\d:\\d\\d:\\d\\d,\\d\\d\\d";
            String _regNum = "\\d+";
            String _regTimeLine = _regTime + " --> " + _regTime;
            String _line;
            BufferedReader _br = new BufferedReader(new InputStreamReader(new FileInputStream(Path)));

            while ((_line = _br.readLine()) != null) {
                if (Pattern.matches(_regNum, _line)) {
                    Subtitle _sub = new Subtitle();

                    _sub.index = Integer.parseInt(_line);

                    _line = _br.readLine();

                    if (_line != null && Pattern.matches(_regTimeLine, _line)) {
                        _sub.beginTime = (Integer.parseInt(_line.substring(0, 2)) * 3600 +
                                Integer.parseInt(_line.substring(3, 5)) * 60 +
                                Integer.parseInt(_line.substring(6, 8))) * 1000 +
                                Integer.parseInt(_line.substring(9, 12));

                        _sub.endTime = (Integer.parseInt(_line.substring(17, 19)) * 3600 +
                                Integer.parseInt(_line.substring(20, 22)) * 60 +
                                Integer.parseInt(_line.substring(23, 25))) * 1000 +
                                Integer.parseInt(_line.substring(26, 29));

                        for (_line = _br.readLine(); _line != null && _line.length() != 0; _line = _br.readLine()) {
                            if (_sub.text == null) {
                                _sub.text = _line;
                            } else {
                                _sub.text += " " + _line;
                            }
                        }
                    }

                    mSrtContainer.add(_sub);
                }
            }
...

完成后,字幕的内容就保存到了Vector中。

2. 外挂字幕的同步

同步我做的比较懒:采用50ms的定周期取Player的播放时间做对比(其实如果采用字幕中的时间轴计算显示/隐藏时间会更省资源)。代码实现上,AddOnSubtitle中,定义一个接口:

public interface OnSubtitleUpdateListener {
        void onSubtitleUpdate(boolean visibility, String text);
    }

Activity注册onSubtitleUpdate回调函数。在AddOnSubtitle中,增加一个延迟触发的事件,定周期(50ms)执行,执行过程中,取当前的Player播放时间,到Vector中取相应的Subtitle,通过回调函数触发Activity显示/隐藏字幕。参考代码如下:

private final int UPDATE_STEP = 50;
    private Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            Message _msg = new Message();
            Bundle _data = new Bundle();
            Subtitle _sub = getSubtitleByPosition(mMediaPlayer.getCurrentPosition() + 150);

            if (_sub != null) {
                _data.putBoolean("visibility", true);
                _data.putString("subtitle", _sub.text);
            } else {
                _data.putBoolean("visibility", false);
            }

            _msg.setData(_data);
            mHandler.sendMessage(_msg);

            mHandler.postDelayed(mRunnable, UPDATE_STEP);
        }
    };
    mHandler = new Handler(new Handler.Callback() {
          @Override
          public boolean handleMessage(Message msg) {
              Bundle _data = msg.getData();

              mOnSubtitleUpdateListener.onSubtitleUpdate(_data.getBoolean("visibility"),
                      _data.getString("subtitle"));
              return false;
          }
      });
3. 外挂字幕的显示

显示就是在xml中,定义一个TextView,回调中设置内容,不再多说。