Ищем рабочий пример addTimedTextSource для добавления субтитров к видео из файла .srt в Android 4.1.

Я пытался использовать файл .srt для синхронизированного источника текста (доступно только в Android 4.1+ http://developer.android.com/about/versions/android-4.1.html#Multimedia). Первая проблема связана с получением файлового дескриптора для файла .srt (в папке с ресурсами, как еще вы могли бы связать его в своем приложении?). Файл сжимается автоматически, поэтому вы даже не сможете увидеть файл, не изменив настройки компиляции или не выполнив пользовательскую сборку. Самым простым решением было переименовать файл .srt в .jpg, чтобы он не сжимался и метод openFD все еще работал. Теперь я добавляю TimedTextSource с помощью:

_myMP.addTimedTextSource(getAssets().openFd("captions.jpg").getFileDescriptor(),   MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);

Теперь файл загружается правильно, и с помощью myMP.getTrackInfo() для получения списка дорожек видно, что после добавления синхронизированного источника текста 6-я дорожка имеет тип «3», который является типом синхронизированной текстовой дорожки. Я использовал selectTrack, чтобы выбрать этот трек, как указано в документации Google, но после этого подписи никогда не появляются и в моем TimedTextListener:

 _myMP.setOnTimedTextListener(new OnTimedTextListener(){
        @Override
        public void onTimedText(MediaPlayer mp, TimedText text) {
                if (text!=null)
                   Log.d("TimedText", text.getText());  
            }       
        });

Срабатывает только один раз (у меня в файле около 20 синхронизированных текстовых событий), но текстовый параметр всегда равен нулю. Я выполнил поиск и не могу найти ни одного примера рабочего кода с использованием timeText, и он не появляется ни в одном примере проектов, буквально нет документации, кроме документов API от Google, но, насколько я могу судить, НИКТО не опубликовал рабочий пример этого еще. Я тестирую это на Google Nexus, обновленном до Android 4.2.


person user1489039    schedule 16.11.2012    source источник
comment
Вы заставили его работать? Я столкнулся с той же проблемой.   -  person user484691    schedule 27.11.2012
comment
нет, я улучшил текстовые события, поместив файл srt прямо на SD-карту (вместо изменения расширения) и загрузив его оттуда, но, похоже, эта функция еще не реализована, вы все еще отвечаете за отрисовку текста, Кроме того, я не уверен, как мне связать его с приложением, чтобы избежать проблемы со сжатием.   -  person user1489039    schedule 27.11.2012
comment
Любые обновления? вы пробовали файл ttml вместо файла формата crt?   -  person MrTexas    schedule 25.01.2013
comment
какое решение вы получили какие-либо ответы.   -  person maxwells    schedule 17.02.2013
comment
Я включил свое полное решение в качестве ответа.   -  person iTech    schedule 18.02.2013


Ответы (3)


Мне удалось заставить это работать, и, поскольку это все еще открытый вопрос, я включу здесь полное решение.

Хотя идея изменить расширение файла, чтобы предотвратить сжатие, хороша, но я предпочитаю копировать файл srt из ресурсов в локальный каталог приложения на устройстве, но в любом случае для полноты вот список расширений, которые выиграли не сжиматься.

".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"

Шаги решения просты:

  1. Создайте экземпляр MediaPlayer и подготовьте его, вызвав MediaPlayer.create() или player.setDataSource(), а затем player.prepare()

  2. Если файлы субтитров еще не существуют на устройстве Android, скопируйте их из папки ресурсов на устройство.

  3. Вызовите player.addTimedTextSource() с первым аргументом String, содержащим полный путь к файлу субтитров на устройстве, и MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP в качестве второго аргумента.

  4. Выберите трек TimedText, вызвав player.selectTrack(), и передайте the index of timedTextType, выполнив поиск TrackInfo[], возвращенного из player.getTrackInfo() (обычно я нахожу 2).

  5. Настройте прослушиватель с помощью player.setOnTimedTextListener(), а затем начните воспроизведение медиафайла player.start().

Вот весь класс:

Чтобы запустить именно этот класс, вам потребуются два файла в папке res/raw sub.srt и video.mp4 (или с любым другим расширением). Затем определите TextView с идентификатором txtDisplay. Наконец, ваш проект/устройство/эмулятор должен поддерживать 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);
}
}

А вот файл subtitle, который я использую в качестве примера:

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

Вот несколько снимков экрана из тестового приложения, показывающих, что TextView изменяется автоматически (т. е. считывается из файла субтитров) по мере воспроизведения медиафайла.

Пример TimedText

Изменить:

Вот код для пример проекта

person iTech    schedule 18.02.2013
comment
привет, я попробовал выше. в onTimedText мы проверяем текст, здесь я получаю ноль. Но еще следует позаботиться. Пожалуйста, дай мне знать. - person maxwells; 02.03.2013
comment
Убедитесь, что ваше видео длится более 23 секунд. Какой телефон/API вы тестируете? - person iTech; 02.03.2013
comment
мой видеофайл составляет около 32 секунд. Я тестирую в эмуляторе, API 17. Но почему вы указываете, что видео длится более 23 секунд, по какой-то конкретной причине. могу ли я получить ваш адрес электронной почты, чтобы я мог отправить свой код через. - person maxwells; 03.03.2013
comment
привет, я могу успешно использовать это с видеофайлом / mp4 в качестве источника данных для медиаплеера, но когда я использую mp3-файл в качестве источника данных, обратный вызов timedText никогда не запускается ... субтитры поддерживаются только для файлов mp4? Пожалуйста, совет - person amj; 10.04.2013
comment
Добавлена ​​ссылка на пример проекта на GitHub - person iTech; 12.04.2013
comment
@Itech привет! Я попробовал образец проекта, который вы загрузили. Но тогда кажется, что есть ошибка? player.getTrackInfo() возвращает только массив длиной 2 (т. е. аудио и видео), поэтому он не включает синхронизированный текст, и поэтому в моем случае текстовое представление никогда не обновлялось. Вы случайно не знаете, почему? Заранее спасибо! - person CodingBird; 01.11.2013
comment
Мое собственное решение, основанное на документах, было почти таким же и не работало. Это решение также не сработало в том же смысле. Я получаю пустые тексты, и я не получаю все из них, которые я должен. API 16 S3. - person Tony; 16.12.2013
comment
@iTech Мы используем несколько дорожек субтитров для видео, которое выбирается с помощью Spinner. Проблема в том, что я не могу переключаться между субтитрами, так как findTrackIndexFor всегда возвращает одно и то же значение: 2. Я запланировал обходной путь, манипулирующий значениями индекса, поскольку я могу удалить дорожку из списка дорожек, не могу выбрать дорожку по имени файла или пути. Есть ли более надежный способ, который вы можете предложить? - person Saro Taşciyan; 08.01.2014
comment
Приведенное выше решение работало в версии 4.2.2, но в версиях 4.1.1 и 4.1.2 параметр text в onTimedText всегда возвращает значение null. - person Code_Yoga; 19.05.2014
comment
@iTech: Как насчет внутренних (внутриполосных) субтитров? - person Behnam; 18.06.2014
comment
Я пробовал, но проблема в том, что после поиска видео субтитры перестают обновляться. Это onTimedText() не вызывается после поиска - person user2498079; 28.01.2015
comment
Да, эта ошибка поиска доставила мне такую ​​головную боль. Пришлось вообще отказаться от timedtext. - person MHDante; 27.07.2015
comment
Это не поддерживает символы UTF-8! - person Dr.jacky; 09.01.2016

РЕДАКТИРОВАТЬ: я должен отметить, что за последние несколько лет версии Android после KitKat стали большей частью доли рынка Android-устройств, использующих приложения. Приведенная ниже реализация была попыткой обеспечить совместимость со старыми устройствами. На данный момент я предлагаю использовать платформу TimedText (которая отлично работала в KitKat) или более новые альтернативы, выпущенные Android, поскольку пользовательское решение может потребовать значительных затрат на обслуживание.


Я провел 2 дня, просматривая исходный код Android, пытаясь сгладить все ошибки, которые вызывал этот TimedText Framework.

Моя рекомендация — вообще пропустить их реализацию. Оно неполно и непоследовательно. В более ранних версиях большая часть синхронизации текста выполняется в собственном медиаплеере, поэтому он подвержен ошибкам состояния.

Моя альтернатива - использовать подкласс 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;
        }
    }
}

Использование:

//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);

В XML-файле вашего макета просто создайте textView, как вы хотите, чтобы отображались субтитры, а затем измените класс на 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"/>

Удачи.

person MHDante    schedule 27.07.2015
comment
можно ли использовать что-то вроде этого setSubSource (uri), используйте URI вместо файла из необработанной папки. Спасибо - person Abdel; 04.08.2016
comment
Да, вам просто нужно изменить getSubtitleFile(int resId) на getSubtitleFile(URI uri). Для этого вам нужно будет открыть входной поток из uri. - person MHDante; 08.08.2016

Чтобы заставить его работать с файлами .mp3, вызовите player.start(); сразу после объявления нового медиаплеера и перед кодом addtimedtext. Сразу после строки ниже

MediaPlayer player = MediaPlayer.create(this, R.raw.video);
person user2774301    schedule 22.09.2013
comment
Пока что это единственный способ заставить это работать с mp3. Кажется неправильным вызывать start до завершения установки. - person j1m; 13.03.2015