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,回调中设置内容,不再多说。