Подтвердить что ты не робот

Поиск рабочего примера 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 docs от google, но, насколько я могу судить, никто не опубликовал рабочий пример. Я тестирую это на Google Nexus, обновленном до Android 4.2

4b9b3361

Ответ 1

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

Хотя идея изменения расширения файла для предотвращения сжатия хорошая, но я предпочитаю копировать файл 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"

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

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

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

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

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

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

Вот полный класс:

Для запуска этого точного класса вам понадобятся два файла под вашей res/raw папкой sub.srt и video.mp4 (или любыми расширениями). Затем определите a TextView с id 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 Example

Edit:

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

Ответ 2

РЕДАКТИРОВАТЬ: Я должен отметить, что в последние несколько лет, пост-KitKat версии Android стали большей частью рынка 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"/>

Удачи.

Ответ 3

когда я приостанавливаю медиапроигрыватель, субтитры также приостанавливаются, но когда я возобновляю субтитры проигрывателя, не возобновляется.

Ответ 4

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

MediaPlayer player = MediaPlayer.create(this, R.raw.video);