1 package android.media;
2 
3 import android.content.Context;
4 import android.media.SubtitleController.Renderer;
5 import android.os.Handler;
6 import android.os.Message;
7 import android.os.Parcel;
8 import android.util.Log;
9 
10 import java.io.BufferedReader;
11 import java.io.ByteArrayInputStream;
12 import java.io.IOException;
13 import java.io.InputStreamReader;
14 import java.io.Reader;
15 import java.io.UnsupportedEncodingException;
16 import java.util.ArrayList;
17 import java.util.List;
18 import java.util.Vector;
19 
20 /** @hide */
21 public class SRTRenderer extends Renderer {
22     private final Context mContext;
23     private final boolean mRender;
24     private final Handler mEventHandler;
25 
26     private WebVttRenderingWidget mRenderingWidget;
27 
SRTRenderer(Context context)28     public SRTRenderer(Context context) {
29         this(context, null);
30     }
31 
SRTRenderer(Context mContext, Handler mEventHandler)32     SRTRenderer(Context mContext, Handler mEventHandler) {
33         this.mContext = mContext;
34         this.mRender = (mEventHandler == null);
35         this.mEventHandler = mEventHandler;
36     }
37 
38     @Override
supports(MediaFormat format)39     public boolean supports(MediaFormat format) {
40         if (format.containsKey(MediaFormat.KEY_MIME)) {
41             if (!format.getString(MediaFormat.KEY_MIME)
42                     .equals(MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP)) {
43                 return false;
44             };
45             return mRender == (format.getInteger(MediaFormat.KEY_IS_TIMED_TEXT, 0) == 0);
46         }
47         return false;
48     }
49 
50     @Override
createTrack(MediaFormat format)51     public SubtitleTrack createTrack(MediaFormat format) {
52         if (mRender && mRenderingWidget == null) {
53             mRenderingWidget = new WebVttRenderingWidget(mContext);
54         }
55 
56         if (mRender) {
57             return new SRTTrack(mRenderingWidget, format);
58         } else {
59             return new SRTTrack(mEventHandler, format);
60         }
61     }
62 }
63 
64 class SRTTrack extends WebVttTrack {
65     private static final int MEDIA_TIMED_TEXT = 99;   // MediaPlayer.MEDIA_TIMED_TEXT
66     private static final int KEY_STRUCT_TEXT = 16;    // TimedText.KEY_STRUCT_TEXT
67     private static final int KEY_START_TIME = 7;      // TimedText.KEY_START_TIME
68     private static final int KEY_LOCAL_SETTING = 102; // TimedText.KEY_START_TIME
69 
70     private static final String TAG = "SRTTrack";
71     private final Handler mEventHandler;
72 
SRTTrack(WebVttRenderingWidget renderingWidget, MediaFormat format)73     SRTTrack(WebVttRenderingWidget renderingWidget, MediaFormat format) {
74         super(renderingWidget, format);
75         mEventHandler = null;
76     }
77 
SRTTrack(Handler eventHandler, MediaFormat format)78     SRTTrack(Handler eventHandler, MediaFormat format) {
79         super(null, format);
80         mEventHandler = eventHandler;
81     }
82 
83     @Override
onData(SubtitleData data)84     protected void onData(SubtitleData data) {
85         try {
86             TextTrackCue cue = new TextTrackCue();
87             cue.mStartTimeMs = data.getStartTimeUs() / 1000;
88             cue.mEndTimeMs = (data.getStartTimeUs() + data.getDurationUs()) / 1000;
89 
90             String paragraph;
91             paragraph = new String(data.getData(), "UTF-8");
92             String[] lines = paragraph.split("\\r?\\n");
93             cue.mLines = new TextTrackCueSpan[lines.length][];
94 
95             int i = 0;
96             for (String line : lines) {
97                 TextTrackCueSpan[] span = new TextTrackCueSpan[] {
98                     new TextTrackCueSpan(line, -1)
99                 };
100                 cue.mLines[i++] = span;
101             }
102 
103             addCue(cue);
104         } catch (UnsupportedEncodingException e) {
105             Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e);
106         }
107     }
108 
109     @Override
onData(byte[] data, boolean eos, long runID)110     public void onData(byte[] data, boolean eos, long runID) {
111         // TODO make reentrant
112         try {
113             Reader r = new InputStreamReader(new ByteArrayInputStream(data), "UTF-8");
114             BufferedReader br = new BufferedReader(r);
115 
116             String header;
117             while ((header = br.readLine()) != null) {
118                 // discard subtitle number
119                 header  = br.readLine();
120                 if (header == null) {
121                     break;
122                 }
123 
124                 TextTrackCue cue = new TextTrackCue();
125                 String[] startEnd = header.split("-->");
126                 cue.mStartTimeMs = parseMs(startEnd[0]);
127                 cue.mEndTimeMs = parseMs(startEnd[1]);
128                 cue.mRunID = runID;
129 
130                 String s;
131                 List<String> paragraph = new ArrayList<String>();
132                 while (!((s = br.readLine()) == null || s.trim().equals(""))) {
133                     paragraph.add(s);
134                 }
135 
136                 int i = 0;
137                 cue.mLines = new TextTrackCueSpan[paragraph.size()][];
138                 cue.mStrings = paragraph.toArray(new String[0]);
139                 for (String line : paragraph) {
140                     TextTrackCueSpan[] span = new TextTrackCueSpan[] {
141                             new TextTrackCueSpan(line, -1)
142                     };
143                     cue.mStrings[i] = line;
144                     cue.mLines[i++] = span;
145                 }
146 
147                 addCue(cue);
148             }
149 
150         } catch (UnsupportedEncodingException e) {
151             Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e);
152         } catch (IOException ioe) {
153             // shouldn't happen
154             Log.e(TAG, ioe.getMessage(), ioe);
155         }
156     }
157 
158     @Override
updateView(Vector<Cue> activeCues)159     public void updateView(Vector<Cue> activeCues) {
160         if (getRenderingWidget() != null) {
161             super.updateView(activeCues);
162             return;
163         }
164 
165         if (mEventHandler == null) {
166             return;
167         }
168 
169         for (Cue cue : activeCues) {
170             TextTrackCue ttc = (TextTrackCue) cue;
171 
172             Parcel parcel = Parcel.obtain();
173             parcel.writeInt(KEY_LOCAL_SETTING);
174             parcel.writeInt(KEY_START_TIME);
175             parcel.writeInt((int) cue.mStartTimeMs);
176 
177             parcel.writeInt(KEY_STRUCT_TEXT);
178             StringBuilder sb = new StringBuilder();
179             for (String line : ttc.mStrings) {
180                 sb.append(line).append('\n');
181             }
182 
183             byte[] buf = sb.toString().getBytes();
184             parcel.writeInt(buf.length);
185             parcel.writeByteArray(buf);
186 
187             Message msg = mEventHandler.obtainMessage(MEDIA_TIMED_TEXT, 0 /* arg1 */, 0 /* arg2 */,
188                     parcel);
189             mEventHandler.sendMessage(msg);
190         }
191         activeCues.clear();
192     }
193 
parseMs(String in)194     private static long parseMs(String in) {
195         long hours = Long.parseLong(in.split(":")[0].trim());
196         long minutes = Long.parseLong(in.split(":")[1].trim());
197         long seconds = Long.parseLong(in.split(":")[2].split(",")[0].trim());
198         long millies = Long.parseLong(in.split(":")[2].split(",")[1].trim());
199 
200         return hours * 60 * 60 * 1000 + minutes * 60 * 1000 + seconds * 1000 + millies;
201 
202     }
203 }
204