Buscando un ejemplo funcional de addTimedTextSource para agregar subtítulos a un video de un archivo.srt en Android 4.1
captions (3)
A las almas perdidas perdiendo tiempo en esto en> 2015:
Pasé dos días mirando a la fuente de Android tratando de resolver todos los errores que causaba este Framework de TimedText.
Mi recomendación es omitir su implementación por completo. Es incompleto e inconsistente. En versiones anteriores, una gran parte de la sincronización de texto se realiza en el reproductor de medios nativo, por lo que es propenso a errores de estado.
Mi alternativa es usar una subclase Textview:
package ca.yourpackage.yourapp;
import android.content.Context;
import android.media.MediaPlayer;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
/**
* Created by MHDante on 2015-07-26.
*/
public class SubtitleView extends TextView implements Runnable{
private static final String TAG = "SubtitleView";
private static final boolean DEBUG = false;
private static final int UPDATE_INTERVAL = 300;
private MediaPlayer player;
private TreeMap<Long, Line> track;
public SubtitleView(Context context) {
super(context);
}
public SubtitleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void run() {
if (player !=null && track!= null){
int seconds = player.getCurrentPosition() / 1000;
setText((DEBUG?"[" + secondsToDuration(seconds) + "] ":"")
+ getTimedText(player.getCurrentPosition()));
}
postDelayed(this, UPDATE_INTERVAL);
}
private String getTimedText(long currentPosition) {
String result = "";
for(Map.Entry<Long, Line> entry: track.entrySet()){
if (currentPosition < entry.getKey()) break;
if (currentPosition < entry.getValue().to) result = entry.getValue().text;
}
return result;
}
// To display the seconds in the duration format 00:00:00
public String secondsToDuration(int seconds) {
return String.format("%02d:%02d:%02d", seconds / 3600,
(seconds % 3600) / 60, (seconds % 60), Locale.US);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
postDelayed(this, 300);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
removeCallbacks(this);
}
public void setPlayer(MediaPlayer player) {
this.player = player;
}
public void setSubSource(int ResID, String mime){
if(mime.equals(MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP))
track = getSubtitleFile(ResID);
else
throw new UnsupportedOperationException("Parser only built for SRT subs");
}
/////////////Utility Methods:
//Based on https://github.com/sannies/mp4parser/
//Apache 2.0 Licence at: https://github.com/sannies/mp4parser/blob/master/LICENSE
public static TreeMap<Long, Line> parse(InputStream is) throws IOException {
LineNumberReader r = new LineNumberReader(new InputStreamReader(is, "UTF-8"));
TreeMap<Long, Line> track = new TreeMap<>();
while ((r.readLine()) != null) /*Read cue number*/{
String timeString = r.readLine();
String lineString = "";
String s;
while (!((s = r.readLine()) == null || s.trim().equals(""))) {
lineString += s + "/n";
}
long startTime = parse(timeString.split("-->")[0]);
long endTime = parse(timeString.split("-->")[1]);
track.put(startTime, new Line(startTime, endTime, lineString));
}
return track;
}
private static long parse(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;
}
private TreeMap<Long, Line> getSubtitleFile(int resId) {
InputStream inputStream = null;
try {
inputStream = getResources().openRawResource(resId);
return parse(inputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
public static class Line {
long from;
long to;
String text;
public Line(long from, long to, String text) {
this.from = from;
this.to = to;
this.text = text;
}
}
}
Uso:
//I used and reccomend asyncPrepare()
MediaPlayer mp = MediaPlayer.create(context, R.raw.video);
SubtitleView subView = (SubtitleView) getViewbyId(R.id.subs_box);
subView.setPlayer(mp);
subView.setSubSource(R.raw.subs_intro, MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);
En su archivo xml de diseño, simplemente cree un textView como desea que se muestren los subtítulos, luego cambie la clase a ca.yourpagckage.yourapp.SubtitleView
<ca.yourpagckage.yourapp.SubtitleView
android:layout_width="300dp"
android:layout_height="300dp"
android:text="Subtitles go Here"
android:id="@+id/subs_box"/>
Buena suerte.
He intentado usar un archivo .srt para una fuente de texto cronometrada (solo disponible en Android 4.1+ http://developer.android.com/about/versions/android-4.1.html#Multimedia ). El primer problema tiene que ver con obtener un descriptor de archivo para el archivo .srt (en la carpeta de activos, ¿cómo lo incluiría en su aplicación?). El archivo se comprime automáticamente, por lo que ni siquiera podrá ver el archivo sin cambiar la configuración de compilación o crear una compilación personalizada. La solución más fácil fue cambiar el nombre del archivo .srt a .jpg para que no se comprimiera y el método openFD aún funcione. Ahora estoy agregando el TimedTextSource con:
_myMP.addTimedTextSource(getAssets().openFd("captions.jpg").getFileDescriptor(), MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);
Ahora el archivo se carga correctamente y usando myMP.getTrackInfo () para obtener una lista de pistas, puede ver que después de agregar la fuente de texto cronometrada, la sexta pista tiene el tipo "3", que es el tipo de pista de texto programado. He utilizado selectTrack para elegir esta pista como se dice en la documentación de google, pero después de hacerlo no aparece ningún título y en mi TimedTextListener:
_myMP.setOnTimedTextListener(new OnTimedTextListener(){
@Override
public void onTimedText(MediaPlayer mp, TimedText text) {
if (text!=null)
Log.d("TimedText", text.getText());
}
});
Dispara solo una vez (tengo como 20 eventos de texto cronometrados en el archivo) pero el parámetro de texto siempre es nulo. He hecho búsquedas y no puedo encontrar un solo código de trabajo que ejemplifique el uso de timeText y no aparece en ningún proyecto de muestra, literalmente no hay documentación más que los documentos de API de Google, pero hasta donde puedo decir, nadie ha publicado un ejemplo de trabajo todavía. Estoy probando esto en un Google Nexus actualizado a Android 4.2
Para que funcione con archivos .mp3, llame a player.start (); Inmediatamente después de declarar un nuevo reproductor multimedia y antes del código de texto adicional. Justo después de la línea de abajo
MediaPlayer player = MediaPlayer.create(this, R.raw.video);
Pude hacer que esto funcionara y dado que todavía es una pregunta abierta, incluiré la solución completa aquí.
Aunque la idea de cambiar la extensión de archivo para evitar la compresión es agradable, pero prefiero copiar el archivo srt
de los recursos al directorio local de la aplicación en el dispositivo, pero de todos modos, para completar, hay una lista de extensiones que ganó no estar comprimido
".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg", ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ". amr", ".awb", ".wma", ".wmv"
Los pasos de la solución son simples:
Cree una instancia de
MediaPlayer
y prepárela llamando aMediaPlayer.create()
oplayer.setDataSource()
luegoplayer.prepare()
Si los archivos de subtítulos aún no existen en el dispositivo Android, cópielo de la carpeta de recursos al dispositivo
Llame a
player.addTimedTextSource()
con el primer argumento, unaString
que contiene la ruta completa del archivo de subtítulos en el dispositivo yMediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP
como segundo argumentoSeleccione la pista de
player.selectTrack()
llamando aplayer.selectTrack()
y pasethe index of timedTextType
buscando enTrackInfo[]
devuelto porplayer.getTrackInfo()
(generalmente lo encuentro2
)Configura un oyente con
player.setOnTimedTextListener()
y luego comienza a reproducir el archivo de mediosplayer.start()
Aquí está la clase completa:
Para ejecutar esta clase exacta, necesitarás dos archivos en la carpeta res/raw
sub.srt
y video.mp4
(o las extensiones). A continuación, defina una vista de TextView
con la id txtDisplay
. Finalmente su proyecto / dispositivo / emulador debe ser compatible con API 16
public class MainActivity extends Activity implements OnTimedTextListener {
private static final String TAG = "TimedTextTest";
private TextView txtDisplay;
private static Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtDisplay = (TextView) findViewById(R.id.txtDisplay);
MediaPlayer player = MediaPlayer.create(this, R.raw.video);
try {
player.addTimedTextSource(getSubtitleFile(R.raw.sub),
MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);
int textTrackIndex = findTrackIndexFor(
TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT, player.getTrackInfo());
if (textTrackIndex >= 0) {
player.selectTrack(textTrackIndex);
} else {
Log.w(TAG, "Cannot find text track!");
}
player.setOnTimedTextListener(this);
player.start();
} catch (Exception e) {
e.printStackTrace();
}
}
private int findTrackIndexFor(int mediaTrackType, TrackInfo[] trackInfo) {
int index = -1;
for (int i = 0; i < trackInfo.length; i++) {
if (trackInfo[i].getTrackType() == mediaTrackType) {
return i;
}
}
return index;
}
private String getSubtitleFile(int resId) {
String fileName = getResources().getResourceEntryName(resId);
File subtitleFile = getFileStreamPath(fileName);
if (subtitleFile.exists()) {
Log.d(TAG, "Subtitle already exists");
return subtitleFile.getAbsolutePath();
}
Log.d(TAG, "Subtitle does not exists, copy it from res/raw");
// Copy the file from the res/raw folder to your app folder on the
// device
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = getResources().openRawResource(resId);
outputStream = new FileOutputStream(subtitleFile, false);
copyFile(inputStream, outputStream);
return subtitleFile.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
} finally {
closeStreams(inputStream, outputStream);
}
return "";
}
private void copyFile(InputStream inputStream, OutputStream outputStream)
throws IOException {
final int BUFFER_SIZE = 1024;
byte[] buffer = new byte[BUFFER_SIZE];
int length = -1;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
}
// A handy method I use to close all the streams
private void closeStreams(Closeable... closeables) {
if (closeables != null) {
for (Closeable stream : closeables) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
@Override
public void onTimedText(final MediaPlayer mp, final TimedText text) {
if (text != null) {
handler.post(new Runnable() {
@Override
public void run() {
int seconds = mp.getCurrentPosition() / 1000;
txtDisplay.setText("[" + secondsToDuration(seconds) + "] "
+ text.getText());
}
});
}
}
// To display the seconds in the duration format 00:00:00
public String secondsToDuration(int seconds) {
return String.format("%02d:%02d:%02d", seconds / 3600,
(seconds % 3600) / 60, (seconds % 60), Locale.US);
}
}
Y aquí está el archivo de subtitle
que estoy usando como ejemplo:
1
00:00:00,220 --> 00:00:01,215
First Text Example
2
00:00:03,148 --> 00:00:05,053
Second Text Example
3
00:00:08,004 --> 00:00:09,884
Third Text Example
4
00:00:11,300 --> 00:00:12,900
Fourth Text Example
5
00:00:15,500 --> 00:00:16,700
Fifth Text Example
6
00:00:18,434 --> 00:00:20,434
Sixth Text Example
7
00:00:22,600 --> 00:00:23,700
Last Text Example
Aquí hay algunas capturas de pantalla de la aplicación de prueba que muestran que TextView
está cambiando automáticamente (es decir, leyendo desde el archivo de subtítulos) a medida que avanza el archivo de medios
Editar:
Aquí está el código para un proyecto de ejemplo