1package android.media;
2
3import android.content.Context;
4import android.media.SubtitleController.Renderer;
5import android.os.Handler;
6import android.os.Message;
7import android.os.Parcel;
8import android.util.Log;
9
10import java.io.BufferedReader;
11import java.io.ByteArrayInputStream;
12import java.io.IOException;
13import java.io.InputStreamReader;
14import java.io.Reader;
15import java.io.UnsupportedEncodingException;
16import java.util.ArrayList;
17import java.util.List;
18import java.util.Vector;
19
20/** @hide */
21public 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
28    public SRTRenderer(Context context) {
29        this(context, null);
30    }
31
32    SRTRenderer(Context mContext, Handler mEventHandler) {
33        this.mContext = mContext;
34        this.mRender = (mEventHandler == null);
35        this.mEventHandler = mEventHandler;
36    }
37
38    @Override
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
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
64class 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
73    SRTTrack(WebVttRenderingWidget renderingWidget, MediaFormat format) {
74        super(renderingWidget, format);
75        mEventHandler = null;
76    }
77
78    SRTTrack(Handler eventHandler, MediaFormat format) {
79        super(null, format);
80        mEventHandler = eventHandler;
81    }
82
83    @Override
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
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
129                String s;
130                List<String> paragraph = new ArrayList<String>();
131                while (!((s = br.readLine()) == null || s.trim().equals(""))) {
132                    paragraph.add(s);
133                }
134
135                int i = 0;
136                cue.mLines = new TextTrackCueSpan[paragraph.size()][];
137                cue.mStrings = paragraph.toArray(new String[0]);
138                for (String line : paragraph) {
139                    TextTrackCueSpan[] span = new TextTrackCueSpan[] {
140                            new TextTrackCueSpan(line, -1)
141                    };
142                    cue.mStrings[i] = line;
143                    cue.mLines[i++] = span;
144                }
145
146                addCue(cue);
147            }
148
149        } catch (UnsupportedEncodingException e) {
150            Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e);
151        } catch (IOException ioe) {
152            // shouldn't happen
153            Log.e(TAG, ioe.getMessage(), ioe);
154        }
155    }
156
157    @Override
158    public void updateView(Vector<Cue> activeCues) {
159        if (getRenderingWidget() != null) {
160            super.updateView(activeCues);
161            return;
162        }
163
164        if (mEventHandler == null) {
165            return;
166        }
167
168        final int _ = 0;
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, _, _, parcel);
188            mEventHandler.sendMessage(msg);
189        }
190        activeCues.clear();
191    }
192
193    private static long parseMs(String in) {
194        long hours = Long.parseLong(in.split(":")[0].trim());
195        long minutes = Long.parseLong(in.split(":")[1].trim());
196        long seconds = Long.parseLong(in.split(":")[2].split(",")[0].trim());
197        long millies = Long.parseLong(in.split(":")[2].split(",")[1].trim());
198
199        return hours * 60 * 60 * 1000 + minutes * 60 * 1000 + seconds * 1000 + millies;
200
201    }
202}
203