Loading media/java/android/media/MediaFormat.java +3 −0 Original line number Diff line number Diff line Loading @@ -483,6 +483,9 @@ public final class MediaFormat { */ public static final String KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle"; /** @hide */ public static final String KEY_IS_TIMED_TEXT = "is-timed-text"; /* package private */ MediaFormat(Map<String, Object> map) { mMap = map; } Loading media/java/android/media/MediaPlayer.java +108 −18 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.media; import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.Application; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; Loading @@ -36,6 +37,8 @@ import android.os.Process; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.system.ErrnoException; import android.system.OsConstants; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; Loading @@ -44,15 +47,22 @@ import android.media.AudioManager; import android.media.MediaFormat; import android.media.MediaTimeProvider; import android.media.SubtitleController; import android.media.SubtitleController.Anchor; import android.media.SubtitleData; import android.media.SubtitleTrack.RenderingWidget; import com.android.internal.app.IAppOpsService; import libcore.io.IoBridge; import libcore.io.Libcore; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.Runnable; import java.net.InetSocketAddress; import java.util.Map; Loading Loading @@ -1846,7 +1856,10 @@ public class MediaPlayer implements SubtitleController.Listener System.arraycopy(trackInfo, 0, allTrackInfo, 0, trackInfo.length); int i = trackInfo.length; for (SubtitleTrack track: mOutOfBandSubtitleTracks) { allTrackInfo[i] = new TrackInfo(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, track.getFormat()); int type = track.isTimedText() ? TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT : TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE; allTrackInfo[i] = new TrackInfo(type, track.getFormat()); ++i; } return allTrackInfo; Loading Loading @@ -1891,7 +1904,7 @@ public class MediaPlayer implements SubtitleController.Listener * A helper function to check if the mime type is supported by media framework. */ private static boolean availableMimeTypeForExternalSource(String mimeType) { if (mimeType == MEDIA_MIMETYPE_TEXT_SUBRIP) { if (MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mimeType)) { return true; } return false; Loading Loading @@ -2147,29 +2160,99 @@ public class MediaPlayer implements SubtitleController.Listener * @throws IllegalArgumentException if the mimeType is not supported. * @throws IllegalStateException if called in an invalid state. */ public void addTimedTextSource(FileDescriptor fd, long offset, long length, String mimeType) public void addTimedTextSource(FileDescriptor fd, long offset, long length, String mime) throws IllegalArgumentException, IllegalStateException { if (!availableMimeTypeForExternalSource(mimeType)) { throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mimeType); if (!availableMimeTypeForExternalSource(mime)) { throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mime); } FileDescriptor fd2; try { fd2 = Libcore.os.dup(fd); } catch (ErrnoException ex) { Log.e(TAG, ex.getMessage(), ex); throw new RuntimeException(ex); } Parcel request = Parcel.obtain(); Parcel reply = Parcel.obtain(); final MediaFormat fFormat = new MediaFormat(); fFormat.setString(MediaFormat.KEY_MIME, mime); fFormat.setInteger(MediaFormat.KEY_IS_TIMED_TEXT, 1); Context context = ActivityThread.currentApplication(); // A MediaPlayer created by a VideoView should already have its mSubtitleController set. if (mSubtitleController == null) { mSubtitleController = new SubtitleController(context, mTimeProvider, this); mSubtitleController.setAnchor(new Anchor() { @Override public void setSubtitleWidget(RenderingWidget subtitleWidget) { } @Override public Looper getSubtitleLooper() { return Looper.getMainLooper(); } }); } if (!mSubtitleController.hasRendererFor(fFormat)) { // test and add not atomic mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler)); } final SubtitleTrack track = mSubtitleController.addTrack(fFormat); mOutOfBandSubtitleTracks.add(track); final FileDescriptor fd3 = fd2; final long offset2 = offset; final long length2 = length; final HandlerThread thread = new HandlerThread( "TimedTextReadThread", Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); thread.start(); Handler handler = new Handler(thread.getLooper()); handler.post(new Runnable() { private int addTrack() { InputStream is = null; final ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { request.writeInterfaceToken(IMEDIA_PLAYER); request.writeInt(INVOKE_ID_ADD_EXTERNAL_SOURCE_FD); request.writeFileDescriptor(fd); request.writeLong(offset); request.writeLong(length); request.writeString(mimeType); invoke(request, reply); Libcore.os.lseek(fd3, offset2, OsConstants.SEEK_SET); byte[] buffer = new byte[4096]; for (int total = 0; total < length2;) { int remain = (int)length2 - total; int bytes = IoBridge.read(fd3, buffer, 0, Math.min(buffer.length, remain)); if (bytes < 0) { break; } else { bos.write(buffer, 0, bytes); total += bytes; } } track.onData(bos.toByteArray(), true /* eos */, ~0 /* runID: keep forever */); return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; } catch (Exception e) { Log.e(TAG, e.getMessage(), e); return MEDIA_INFO_TIMED_TEXT_ERROR; } finally { request.recycle(); reply.recycle(); if (is != null) { try { is.close(); } catch (IOException e) { Log.e(TAG, e.getMessage(), e); } } } } public void run() { int res = addTrack(); if (mEventHandler != null) { Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); mEventHandler.sendMessage(m); } thread.getLooper().quitSafely(); } }); } /** * Returns the index of the audio, video, or subtitle track currently selected for playback, * The return value is an index into the array returned by {@link #getTrackInfo()}, and can Loading Loading @@ -2275,6 +2358,13 @@ public class MediaPlayer implements SubtitleController.Listener if (mSubtitleController != null && track != null) { if (select) { if (track.isTimedText()) { int ttIndex = getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT); if (ttIndex >= 0 && ttIndex < mInbandSubtitleTracks.length) { // deselect inband counterpart selectOrDeselectInbandTrack(ttIndex, false); } } mSubtitleController.selectTrack(track); } else if (mSubtitleController.getSelectedTrack() == track) { mSubtitleController.selectTrack(null); Loading media/java/android/media/SRTRenderer.java 0 → 100644 +202 −0 Original line number Diff line number Diff line package android.media; import android.content.Context; import android.media.SubtitleController.Renderer; import android.os.Handler; import android.os.Message; import android.os.Parcel; import android.util.Log; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.util.Vector; /** @hide */ public class SRTRenderer extends Renderer { private final Context mContext; private final boolean mRender; private final Handler mEventHandler; private WebVttRenderingWidget mRenderingWidget; public SRTRenderer(Context context) { this(context, null); } SRTRenderer(Context mContext, Handler mEventHandler) { this.mContext = mContext; this.mRender = (mEventHandler == null); this.mEventHandler = mEventHandler; } @Override public boolean supports(MediaFormat format) { if (format.containsKey(MediaFormat.KEY_MIME)) { if (!format.getString(MediaFormat.KEY_MIME) .equals(MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP)) { return false; }; return mRender == (format.getInteger(MediaFormat.KEY_IS_TIMED_TEXT, 0) == 0); } return false; } @Override public SubtitleTrack createTrack(MediaFormat format) { if (mRender && mRenderingWidget == null) { mRenderingWidget = new WebVttRenderingWidget(mContext); } if (mRender) { return new SRTTrack(mRenderingWidget, format); } else { return new SRTTrack(mEventHandler, format); } } } class SRTTrack extends WebVttTrack { private static final int MEDIA_TIMED_TEXT = 99; // MediaPlayer.MEDIA_TIMED_TEXT private static final int KEY_STRUCT_TEXT = 16; // TimedText.KEY_STRUCT_TEXT private static final int KEY_START_TIME = 7; // TimedText.KEY_START_TIME private static final int KEY_LOCAL_SETTING = 102; // TimedText.KEY_START_TIME private static final String TAG = "SRTTrack"; private final Handler mEventHandler; SRTTrack(WebVttRenderingWidget renderingWidget, MediaFormat format) { super(renderingWidget, format); mEventHandler = null; } SRTTrack(Handler eventHandler, MediaFormat format) { super(null, format); mEventHandler = eventHandler; } @Override protected void onData(SubtitleData data) { try { TextTrackCue cue = new TextTrackCue(); cue.mStartTimeMs = data.getStartTimeUs() / 1000; cue.mEndTimeMs = (data.getStartTimeUs() + data.getDurationUs()) / 1000; String paragraph; paragraph = new String(data.getData(), "UTF-8"); String[] lines = paragraph.split("\\r?\\n"); cue.mLines = new TextTrackCueSpan[lines.length][]; int i = 0; for (String line : lines) { TextTrackCueSpan[] span = new TextTrackCueSpan[] { new TextTrackCueSpan(line, -1) }; cue.mLines[i++] = span; } addCue(cue); } catch (UnsupportedEncodingException e) { Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e); } } @Override public void onData(byte[] data, boolean eos, long runID) { // TODO make reentrant try { Reader r = new InputStreamReader(new ByteArrayInputStream(data), "UTF-8"); BufferedReader br = new BufferedReader(r); String header; while ((header = br.readLine()) != null) { // discard subtitle number header = br.readLine(); if (header == null) { break; } TextTrackCue cue = new TextTrackCue(); String[] startEnd = header.split("-->"); cue.mStartTimeMs = parseMs(startEnd[0]); cue.mEndTimeMs = parseMs(startEnd[1]); String s; List<String> paragraph = new ArrayList<String>(); while (!((s = br.readLine()) == null || s.trim().equals(""))) { paragraph.add(s); } int i = 0; cue.mLines = new TextTrackCueSpan[paragraph.size()][]; cue.mStrings = paragraph.toArray(new String[0]); for (String line : paragraph) { TextTrackCueSpan[] span = new TextTrackCueSpan[] { new TextTrackCueSpan(line, -1) }; cue.mStrings[i] = line; cue.mLines[i++] = span; } addCue(cue); } } catch (UnsupportedEncodingException e) { Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e); } catch (IOException ioe) { // shouldn't happen Log.e(TAG, ioe.getMessage(), ioe); } } @Override public void updateView(Vector<Cue> activeCues) { if (getRenderingWidget() != null) { super.updateView(activeCues); return; } if (mEventHandler == null) { return; } final int _ = 0; for (Cue cue : activeCues) { TextTrackCue ttc = (TextTrackCue) cue; Parcel parcel = Parcel.obtain(); parcel.writeInt(KEY_LOCAL_SETTING); parcel.writeInt(KEY_START_TIME); parcel.writeInt((int) cue.mStartTimeMs); parcel.writeInt(KEY_STRUCT_TEXT); StringBuilder sb = new StringBuilder(); for (String line : ttc.mStrings) { sb.append(line).append('\n'); } byte[] buf = sb.toString().getBytes(); parcel.writeInt(buf.length); parcel.writeByteArray(buf); Message msg = mEventHandler.obtainMessage(MEDIA_TIMED_TEXT, _, _, parcel); mEventHandler.sendMessage(msg); } activeCues.clear(); } private static long parseMs(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; } } media/java/android/media/SubtitleController.java +13 −0 Original line number Diff line number Diff line Loading @@ -420,6 +420,19 @@ public class SubtitleController { } } /** @hide */ public boolean hasRendererFor(MediaFormat format) { synchronized(mRenderers) { // TODO how to get available renderers in the system for (Renderer renderer: mRenderers) { if (renderer.supports(format)) { return true; } } return false; } } /** * Subtitle anchor, an object that is able to display a subtitle renderer, * e.g. a VideoView. Loading media/java/android/media/SubtitleTrack.java +14 −2 Original line number Diff line number Diff line Loading @@ -274,7 +274,10 @@ public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeList } mVisible = true; getRenderingWidget().setVisible(true); RenderingWidget renderingWidget = getRenderingWidget(); if (renderingWidget != null) { renderingWidget.setVisible(true); } if (mTimeProvider != null) { mTimeProvider.scheduleUpdate(this); } Loading @@ -289,7 +292,10 @@ public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeList if (mTimeProvider != null) { mTimeProvider.cancelNotifications(this); } getRenderingWidget().setVisible(false); RenderingWidget renderingWidget = getRenderingWidget(); if (renderingWidget != null) { renderingWidget.setVisible(false); } mVisible = false; } Loading Loading @@ -602,6 +608,12 @@ public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeList } } /** @hide whether this is a text track who fires events instead getting rendered */ public boolean isTimedText() { return getRenderingWidget() == null; } /** @hide */ private static class Run { public Cue mFirstCue; Loading Loading
media/java/android/media/MediaFormat.java +3 −0 Original line number Diff line number Diff line Loading @@ -483,6 +483,9 @@ public final class MediaFormat { */ public static final String KEY_IS_FORCED_SUBTITLE = "is-forced-subtitle"; /** @hide */ public static final String KEY_IS_TIMED_TEXT = "is-timed-text"; /* package private */ MediaFormat(Map<String, Object> map) { mMap = map; } Loading
media/java/android/media/MediaPlayer.java +108 −18 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.media; import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.Application; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; Loading @@ -36,6 +37,8 @@ import android.os.Process; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.system.ErrnoException; import android.system.OsConstants; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; Loading @@ -44,15 +47,22 @@ import android.media.AudioManager; import android.media.MediaFormat; import android.media.MediaTimeProvider; import android.media.SubtitleController; import android.media.SubtitleController.Anchor; import android.media.SubtitleData; import android.media.SubtitleTrack.RenderingWidget; import com.android.internal.app.IAppOpsService; import libcore.io.IoBridge; import libcore.io.Libcore; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.Runnable; import java.net.InetSocketAddress; import java.util.Map; Loading Loading @@ -1846,7 +1856,10 @@ public class MediaPlayer implements SubtitleController.Listener System.arraycopy(trackInfo, 0, allTrackInfo, 0, trackInfo.length); int i = trackInfo.length; for (SubtitleTrack track: mOutOfBandSubtitleTracks) { allTrackInfo[i] = new TrackInfo(TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE, track.getFormat()); int type = track.isTimedText() ? TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT : TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE; allTrackInfo[i] = new TrackInfo(type, track.getFormat()); ++i; } return allTrackInfo; Loading Loading @@ -1891,7 +1904,7 @@ public class MediaPlayer implements SubtitleController.Listener * A helper function to check if the mime type is supported by media framework. */ private static boolean availableMimeTypeForExternalSource(String mimeType) { if (mimeType == MEDIA_MIMETYPE_TEXT_SUBRIP) { if (MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mimeType)) { return true; } return false; Loading Loading @@ -2147,29 +2160,99 @@ public class MediaPlayer implements SubtitleController.Listener * @throws IllegalArgumentException if the mimeType is not supported. * @throws IllegalStateException if called in an invalid state. */ public void addTimedTextSource(FileDescriptor fd, long offset, long length, String mimeType) public void addTimedTextSource(FileDescriptor fd, long offset, long length, String mime) throws IllegalArgumentException, IllegalStateException { if (!availableMimeTypeForExternalSource(mimeType)) { throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mimeType); if (!availableMimeTypeForExternalSource(mime)) { throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mime); } FileDescriptor fd2; try { fd2 = Libcore.os.dup(fd); } catch (ErrnoException ex) { Log.e(TAG, ex.getMessage(), ex); throw new RuntimeException(ex); } Parcel request = Parcel.obtain(); Parcel reply = Parcel.obtain(); final MediaFormat fFormat = new MediaFormat(); fFormat.setString(MediaFormat.KEY_MIME, mime); fFormat.setInteger(MediaFormat.KEY_IS_TIMED_TEXT, 1); Context context = ActivityThread.currentApplication(); // A MediaPlayer created by a VideoView should already have its mSubtitleController set. if (mSubtitleController == null) { mSubtitleController = new SubtitleController(context, mTimeProvider, this); mSubtitleController.setAnchor(new Anchor() { @Override public void setSubtitleWidget(RenderingWidget subtitleWidget) { } @Override public Looper getSubtitleLooper() { return Looper.getMainLooper(); } }); } if (!mSubtitleController.hasRendererFor(fFormat)) { // test and add not atomic mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler)); } final SubtitleTrack track = mSubtitleController.addTrack(fFormat); mOutOfBandSubtitleTracks.add(track); final FileDescriptor fd3 = fd2; final long offset2 = offset; final long length2 = length; final HandlerThread thread = new HandlerThread( "TimedTextReadThread", Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE); thread.start(); Handler handler = new Handler(thread.getLooper()); handler.post(new Runnable() { private int addTrack() { InputStream is = null; final ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { request.writeInterfaceToken(IMEDIA_PLAYER); request.writeInt(INVOKE_ID_ADD_EXTERNAL_SOURCE_FD); request.writeFileDescriptor(fd); request.writeLong(offset); request.writeLong(length); request.writeString(mimeType); invoke(request, reply); Libcore.os.lseek(fd3, offset2, OsConstants.SEEK_SET); byte[] buffer = new byte[4096]; for (int total = 0; total < length2;) { int remain = (int)length2 - total; int bytes = IoBridge.read(fd3, buffer, 0, Math.min(buffer.length, remain)); if (bytes < 0) { break; } else { bos.write(buffer, 0, bytes); total += bytes; } } track.onData(bos.toByteArray(), true /* eos */, ~0 /* runID: keep forever */); return MEDIA_INFO_EXTERNAL_METADATA_UPDATE; } catch (Exception e) { Log.e(TAG, e.getMessage(), e); return MEDIA_INFO_TIMED_TEXT_ERROR; } finally { request.recycle(); reply.recycle(); if (is != null) { try { is.close(); } catch (IOException e) { Log.e(TAG, e.getMessage(), e); } } } } public void run() { int res = addTrack(); if (mEventHandler != null) { Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null); mEventHandler.sendMessage(m); } thread.getLooper().quitSafely(); } }); } /** * Returns the index of the audio, video, or subtitle track currently selected for playback, * The return value is an index into the array returned by {@link #getTrackInfo()}, and can Loading Loading @@ -2275,6 +2358,13 @@ public class MediaPlayer implements SubtitleController.Listener if (mSubtitleController != null && track != null) { if (select) { if (track.isTimedText()) { int ttIndex = getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT); if (ttIndex >= 0 && ttIndex < mInbandSubtitleTracks.length) { // deselect inband counterpart selectOrDeselectInbandTrack(ttIndex, false); } } mSubtitleController.selectTrack(track); } else if (mSubtitleController.getSelectedTrack() == track) { mSubtitleController.selectTrack(null); Loading
media/java/android/media/SRTRenderer.java 0 → 100644 +202 −0 Original line number Diff line number Diff line package android.media; import android.content.Context; import android.media.SubtitleController.Renderer; import android.os.Handler; import android.os.Message; import android.os.Parcel; import android.util.Log; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.util.Vector; /** @hide */ public class SRTRenderer extends Renderer { private final Context mContext; private final boolean mRender; private final Handler mEventHandler; private WebVttRenderingWidget mRenderingWidget; public SRTRenderer(Context context) { this(context, null); } SRTRenderer(Context mContext, Handler mEventHandler) { this.mContext = mContext; this.mRender = (mEventHandler == null); this.mEventHandler = mEventHandler; } @Override public boolean supports(MediaFormat format) { if (format.containsKey(MediaFormat.KEY_MIME)) { if (!format.getString(MediaFormat.KEY_MIME) .equals(MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP)) { return false; }; return mRender == (format.getInteger(MediaFormat.KEY_IS_TIMED_TEXT, 0) == 0); } return false; } @Override public SubtitleTrack createTrack(MediaFormat format) { if (mRender && mRenderingWidget == null) { mRenderingWidget = new WebVttRenderingWidget(mContext); } if (mRender) { return new SRTTrack(mRenderingWidget, format); } else { return new SRTTrack(mEventHandler, format); } } } class SRTTrack extends WebVttTrack { private static final int MEDIA_TIMED_TEXT = 99; // MediaPlayer.MEDIA_TIMED_TEXT private static final int KEY_STRUCT_TEXT = 16; // TimedText.KEY_STRUCT_TEXT private static final int KEY_START_TIME = 7; // TimedText.KEY_START_TIME private static final int KEY_LOCAL_SETTING = 102; // TimedText.KEY_START_TIME private static final String TAG = "SRTTrack"; private final Handler mEventHandler; SRTTrack(WebVttRenderingWidget renderingWidget, MediaFormat format) { super(renderingWidget, format); mEventHandler = null; } SRTTrack(Handler eventHandler, MediaFormat format) { super(null, format); mEventHandler = eventHandler; } @Override protected void onData(SubtitleData data) { try { TextTrackCue cue = new TextTrackCue(); cue.mStartTimeMs = data.getStartTimeUs() / 1000; cue.mEndTimeMs = (data.getStartTimeUs() + data.getDurationUs()) / 1000; String paragraph; paragraph = new String(data.getData(), "UTF-8"); String[] lines = paragraph.split("\\r?\\n"); cue.mLines = new TextTrackCueSpan[lines.length][]; int i = 0; for (String line : lines) { TextTrackCueSpan[] span = new TextTrackCueSpan[] { new TextTrackCueSpan(line, -1) }; cue.mLines[i++] = span; } addCue(cue); } catch (UnsupportedEncodingException e) { Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e); } } @Override public void onData(byte[] data, boolean eos, long runID) { // TODO make reentrant try { Reader r = new InputStreamReader(new ByteArrayInputStream(data), "UTF-8"); BufferedReader br = new BufferedReader(r); String header; while ((header = br.readLine()) != null) { // discard subtitle number header = br.readLine(); if (header == null) { break; } TextTrackCue cue = new TextTrackCue(); String[] startEnd = header.split("-->"); cue.mStartTimeMs = parseMs(startEnd[0]); cue.mEndTimeMs = parseMs(startEnd[1]); String s; List<String> paragraph = new ArrayList<String>(); while (!((s = br.readLine()) == null || s.trim().equals(""))) { paragraph.add(s); } int i = 0; cue.mLines = new TextTrackCueSpan[paragraph.size()][]; cue.mStrings = paragraph.toArray(new String[0]); for (String line : paragraph) { TextTrackCueSpan[] span = new TextTrackCueSpan[] { new TextTrackCueSpan(line, -1) }; cue.mStrings[i] = line; cue.mLines[i++] = span; } addCue(cue); } } catch (UnsupportedEncodingException e) { Log.w(TAG, "subtitle data is not UTF-8 encoded: " + e); } catch (IOException ioe) { // shouldn't happen Log.e(TAG, ioe.getMessage(), ioe); } } @Override public void updateView(Vector<Cue> activeCues) { if (getRenderingWidget() != null) { super.updateView(activeCues); return; } if (mEventHandler == null) { return; } final int _ = 0; for (Cue cue : activeCues) { TextTrackCue ttc = (TextTrackCue) cue; Parcel parcel = Parcel.obtain(); parcel.writeInt(KEY_LOCAL_SETTING); parcel.writeInt(KEY_START_TIME); parcel.writeInt((int) cue.mStartTimeMs); parcel.writeInt(KEY_STRUCT_TEXT); StringBuilder sb = new StringBuilder(); for (String line : ttc.mStrings) { sb.append(line).append('\n'); } byte[] buf = sb.toString().getBytes(); parcel.writeInt(buf.length); parcel.writeByteArray(buf); Message msg = mEventHandler.obtainMessage(MEDIA_TIMED_TEXT, _, _, parcel); mEventHandler.sendMessage(msg); } activeCues.clear(); } private static long parseMs(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; } }
media/java/android/media/SubtitleController.java +13 −0 Original line number Diff line number Diff line Loading @@ -420,6 +420,19 @@ public class SubtitleController { } } /** @hide */ public boolean hasRendererFor(MediaFormat format) { synchronized(mRenderers) { // TODO how to get available renderers in the system for (Renderer renderer: mRenderers) { if (renderer.supports(format)) { return true; } } return false; } } /** * Subtitle anchor, an object that is able to display a subtitle renderer, * e.g. a VideoView. Loading
media/java/android/media/SubtitleTrack.java +14 −2 Original line number Diff line number Diff line Loading @@ -274,7 +274,10 @@ public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeList } mVisible = true; getRenderingWidget().setVisible(true); RenderingWidget renderingWidget = getRenderingWidget(); if (renderingWidget != null) { renderingWidget.setVisible(true); } if (mTimeProvider != null) { mTimeProvider.scheduleUpdate(this); } Loading @@ -289,7 +292,10 @@ public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeList if (mTimeProvider != null) { mTimeProvider.cancelNotifications(this); } getRenderingWidget().setVisible(false); RenderingWidget renderingWidget = getRenderingWidget(); if (renderingWidget != null) { renderingWidget.setVisible(false); } mVisible = false; } Loading Loading @@ -602,6 +608,12 @@ public abstract class SubtitleTrack implements MediaTimeProvider.OnMediaTimeList } } /** @hide whether this is a text track who fires events instead getting rendered */ public boolean isTimedText() { return getRenderingWidget() == null; } /** @hide */ private static class Run { public Cue mFirstCue; Loading