/* * Copyright (C) 2021 pedroSG94. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pedro.encoder.input.decoder; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.media.MediaCodec; import android.media.MediaDataSource; import android.media.MediaExtractor; import android.media.MediaFormat; import android.net.Uri; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; import android.view.Surface; import androidx.annotation.RequiresApi; import java.io.FileDescriptor; import java.io.IOException; import java.util.Map; public abstract class BaseDecoder { protected static String TAG = "BaseDecoder"; protected LoopFileInterface loopFileInterface; protected MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); protected MediaExtractor extractor; protected MediaCodec codec; protected volatile boolean running = false; protected MediaFormat mediaFormat; private HandlerThread handlerThread; protected String mime = ""; protected boolean loopMode = false; protected volatile long seekTime = 0; protected volatile long startMs = 0; protected long duration; public BaseDecoder(LoopFileInterface loopFileInterface) { this.loopFileInterface = loopFileInterface; } public boolean initExtractor(String filePath) throws IOException { extractor = new MediaExtractor(); extractor.setDataSource(filePath); return extract(extractor); } public boolean initExtractor(FileDescriptor fileDescriptor) throws IOException { extractor = new MediaExtractor(); extractor.setDataSource(fileDescriptor); return extract(extractor); } @RequiresApi(api = Build.VERSION_CODES.N) public boolean initExtractor(AssetFileDescriptor assetFileDescriptor) throws IOException { extractor = new MediaExtractor(); extractor.setDataSource(assetFileDescriptor); return extract(extractor); } @RequiresApi(api = Build.VERSION_CODES.M) public boolean initExtractor(MediaDataSource mediaDataSource) throws IOException { extractor = new MediaExtractor(); extractor.setDataSource(mediaDataSource); return extract(extractor); } public boolean initExtractor(String filePath, Map headers) throws IOException { extractor = new MediaExtractor(); extractor.setDataSource(filePath, headers); return extract(extractor); } public boolean initExtractor(FileDescriptor fileDescriptor, long offset, long length) throws IOException { extractor = new MediaExtractor(); extractor.setDataSource(fileDescriptor, offset, length); return extract(extractor); } public boolean initExtractor(Context context, Uri uri, Map headers) throws IOException { extractor = new MediaExtractor(); extractor.setDataSource(context, uri, headers); return extract(extractor); } public void start() { Log.i(TAG, "start decoder"); running = true; handlerThread = new HandlerThread(TAG); handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()); codec.start(); handler.post(new Runnable() { @Override public void run() { try { decode(); } catch (IllegalStateException e) { Log.i(TAG, "Decoding error", e); } catch (NullPointerException e) { Log.i(TAG, "Decoder maybe was stopped"); Log.i(TAG, "Decoding error", e); } } }); } public void stop() { Log.i(TAG, "stop decoder"); running = false; stopDecoder(); if (extractor != null) { extractor.release(); extractor = null; mime = ""; } } protected boolean prepare(Surface surface) { try { codec = MediaCodec.createDecoderByType(mime); codec.configure(mediaFormat, surface, null, 0); return true; } catch (IOException e) { Log.e(TAG, "Prepare decoder error:", e); return false; } } protected void resetCodec(Surface surface) { boolean wasRunning = running; stopDecoder(); if (extractor != null) seekTime = extractor.getSampleTime() / 1000; prepare(surface); if (wasRunning) { start(); } } protected void stopDecoder() { running = false; seekTime = 0; if (handlerThread != null) { if (handlerThread.getLooper() != null) { if (handlerThread.getLooper().getThread() != null) { handlerThread.getLooper().getThread().interrupt(); } handlerThread.getLooper().quit(); } handlerThread.quit(); if (codec != null) { try { codec.flush(); } catch (IllegalStateException ignored) { } } //wait for thread to die for 500ms. try { handlerThread.getLooper().getThread().join(500); } catch (Exception ignored) { } handlerThread = null; } try { codec.stop(); codec.release(); codec = null; } catch (IllegalStateException | NullPointerException e) { codec = null; } } public void moveTo(double time) { extractor.seekTo((long) (time * 10E5), MediaExtractor.SEEK_TO_CLOSEST_SYNC); seekTime = extractor.getSampleTime() / 1000; startMs = System.currentTimeMillis(); } public void setLoopMode(boolean loopMode) { this.loopMode = loopMode; } public boolean isLoopMode() { return loopMode; } public double getDuration() { return duration / 10E5; } public double getTime() { if (running) { return extractor.getSampleTime() / 10E5; } else { return 0; } } protected abstract boolean extract(MediaExtractor extractor); protected abstract void decode(); }