天天看點

HarmonyOS實作音視訊分離合成和截取

音視訊分離合理直播分享

權限配置:

"reqPermissions": [
  {"name": "ohos.permission.WRITE_MEDIA"},
  {"name": "ohos.permission.READ_MEDIA"}
]

requestPermissionsFromUser(
                new String[]{SystemPermission.WRITE_MEDIA
                        ,SystemPermission.READ_MEDIA},0);
           

界面布局:

首頁:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <Button
        ohos:id="$+id:go_separate_btn"
        ohos:text="音視訊分離和合成"
        ohos:width="200vp"
        ohos:height="match_content"
        ohos:padding="8vp"
        ohos:background_element="blue"
        ohos:text_color="#fff"
        ohos:text_size="20fp"/>

    <Button
        ohos:id="$+id:go_cut_btn"
        ohos:text="視訊剪切"
        ohos:width="200vp"
        ohos:height="match_content"
        ohos:padding="8vp"
        ohos:background_element="blue"
        ohos:text_color="#fff"
        ohos:top_margin="15vp"
        ohos:text_size="20fp"/>

    <Button
        ohos:id="$+id:go_join_btn"
        ohos:text="視訊拼接"
        ohos:width="200vp"
        ohos:height="match_content"
        ohos:padding="8vp"
        ohos:background_element="blue"
        ohos:text_color="#fff"
        ohos:top_margin="15vp"
        ohos:text_size="20fp"/>

</DirectionalLayout>
           

分離合成SeparateAbility:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="start"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:text_size="15fp"
        ohos:text_color="#000"
        ohos:text="提供音頻資料:"
        ohos:text_alignment="left"
        />

    <DirectionalLayout
        ohos:id="$+id:play_directionalLayout1"
        ohos:width="match_parent"
        ohos:height="240vp"
        ohos:alignment="center"
        ohos:background_element="#000000"
        />
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:alignment="vertical_center"
        ohos:orientation="horizontal"
        >

        <Text
            ohos:id="$+id:current_time1"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>

        <Slider
            ohos:id="$+id:progress1"
            ohos:height="35vp"
            ohos:width="0vp"
            ohos:start_margin="5vp"
            ohos:end_margin="5vp"
            ohos:orientation="horizontal"
            ohos:progress_element="#FF6103"
            ohos:progress_width="5vp"
            ohos:weight="1"/>

        <Text
            ohos:id="$+id:end_time1"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>
    </DirectionalLayout>

    <DirectionalLayout
        ohos:width="match_parent"
        ohos:height="match_content"
        ohos:top_margin="0vp"
        ohos:left_margin="20vp"
        ohos:right_margin="20vp"
        ohos:layout_alignment="horizontal_center"
        ohos:orientation="horizontal">

        <Button
            ohos:id="$+id:start_time_btn1"
            ohos:text="确定開始時間"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="5vp"
            ohos:right_margin="10vp"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="18fp"/>

        <Button
            ohos:id="$+id:end_time_btn1"
            ohos:text="确定結束時間"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="5vp"
            ohos:left_margin="10vp"
            ohos:layout_alignment="vertical_center"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="18fp"/>
    </DirectionalLayout>


    <Text
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:text_size="15fp"
        ohos:text_color="#000"
        ohos:text="提供視訊資料:"
        ohos:text_alignment="left"
        ohos:top_margin="10vp"
        />

    <DirectionalLayout
        ohos:id="$+id:play_directionalLayout2"
        ohos:width="match_parent"
        ohos:height="240vp"
        ohos:alignment="center"
        ohos:background_element="#000000"
        />
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:alignment="vertical_center"
        ohos:orientation="horizontal"
        >

        <Text
            ohos:id="$+id:current_time2"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>

        <Slider
            ohos:id="$+id:progress2"
            ohos:height="35vp"
            ohos:width="0vp"
            ohos:start_margin="5vp"
            ohos:end_margin="5vp"
            ohos:orientation="horizontal"
            ohos:progress_element="#FF6103"
            ohos:progress_width="5vp"
            ohos:weight="1"/>

        <Text
            ohos:id="$+id:end_time2"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>
    </DirectionalLayout>

    <DirectionalLayout
        ohos:width="match_parent"
        ohos:height="match_content"
        ohos:top_margin="0vp"
        ohos:left_margin="20vp"
        ohos:right_margin="20vp"
        ohos:layout_alignment="horizontal_center"
        ohos:orientation="horizontal">

        <Button
            ohos:id="$+id:start_time_btn2"
            ohos:text="确定開始時間"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="5vp"
            ohos:right_margin="10vp"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="18fp"/>

        <Button
            ohos:id="$+id:end_time_btn2"
            ohos:text="确定結束時間"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="5vp"
            ohos:left_margin="10vp"
            ohos:layout_alignment="vertical_center"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="18fp"/>
    </DirectionalLayout>
    <Button
        ohos:id="$+id:separate_btn"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:padding="5vp"
        ohos:text="開始分離合成"
        ohos:text_size="18fp"
        ohos:background_element="#ff0000"
        ohos:top_margin="15vp"
        ohos:layout_alignment="center"
        />
</DirectionalLayout>
           

視訊剪切:

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:orientation="vertical">

    <Text
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:text_size="20fp"
        ohos:text_color="#000"
        ohos:text="原視訊"
        ohos:text_alignment="center"
        />

    <DirectionalLayout
        ohos:id="$+id:cut_play_directionalLayout"
        ohos:width="match_parent"
        ohos:height="240vp"
        ohos:alignment="center"
        ohos:background_element="#000000"
        />
    <DirectionalLayout
        ohos:height="match_content"
        ohos:width="match_parent"
        ohos:alignment="vertical_center"
        ohos:orientation="horizontal"
        >

        <Text
            ohos:id="$+id:current_time"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>

        <Slider
            ohos:id="$+id:progress"
            ohos:height="35vp"
            ohos:width="0vp"
            ohos:start_margin="5vp"
            ohos:end_margin="5vp"
            ohos:orientation="horizontal"
            ohos:progress_element="#FF6103"
            ohos:progress_width="5vp"
            ohos:weight="1"/>

        <Text
            ohos:id="$+id:end_time"
            ohos:height="match_content"
            ohos:width="match_content"
            ohos:text="00:00:00"
            ohos:text_color="#000"
            ohos:text_size="12vp"/>
    </DirectionalLayout>

    <DirectionalLayout
        ohos:width="match_parent"
        ohos:height="match_content"
        ohos:top_margin="0vp"
        ohos:left_margin="20vp"
        ohos:right_margin="20vp"
        ohos:layout_alignment="horizontal_center"
        ohos:orientation="horizontal">

        <Button
            ohos:id="$+id:start_time_btn"
            ohos:text="确定開始時間"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="8vp"
            ohos:right_margin="10vp"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="20fp"/>

        <Button
            ohos:id="$+id:end_time_btn"
            ohos:text="确定結束時間"
            ohos:width="0vp"
            ohos:weight="1"
            ohos:height="match_content"
            ohos:padding="8vp"
            ohos:left_margin="10vp"
            ohos:layout_alignment="vertical_center"
            ohos:background_element="blue"
            ohos:text_color="#fff"
            ohos:text_size="20fp"/>
    </DirectionalLayout>

    <Button
        ohos:id="$+id:cut_btn"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:padding="8vp"
        ohos:text="開始剪切"
        ohos:text_size="22fp"
        ohos:background_element="#ff0000"
        ohos:top_margin="15vp"
        ohos:layout_alignment="center"
        />

</DirectionalLayout>
           

工具類:

讀寫外部存儲公共目錄工具:

public class StorageFileUtils {
   public enum MediaType{
        VIDEO,
        IMAGE,
        AUDIO,
        DOWNLOADS
    }

    /**
     * 擷取媒體檔案儲存的外部存儲公共目錄的fd,為了讀,查詢
     * @param context
     * @param fileName
     * @return
     */
    public static FileDescriptor getPublicFileFdForRead(Context context,MediaType type,String fileName){
        DataAbilityHelper helper = DataAbilityHelper.creator(context); //api7以後,create方法替代了
        Uri externalDataAbilityUri = null;
        switch (type){
            case VIDEO:
                externalDataAbilityUri = AVStorage.Video.Media.EXTERNAL_DATA_ABILITY_URI;
                break;
            case IMAGE:
                externalDataAbilityUri = AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI;
                break;
            case DOWNLOADS:
                externalDataAbilityUri = AVStorage.Downloads.EXTERNAL_DATA_ABILITY_URI;
                break;
            case AUDIO:
                externalDataAbilityUri = AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI;
                break;
        }
        ResultSet rs = null;
        FileDescriptor fileDescriptor = null;
        try {
            DataAbilityPredicates predicates = 
                new DataAbilityPredicates("_display_name = '" + fileName + "'");
            rs = helper.query(externalDataAbilityUri,new String[]{AVStorage.Video.Media.ID},predicates);
            System.out.println("rs count:" + rs.getRowCount());
            if(rs != null){
                while (rs.goToNextRow()) {
                    int mediaId = rs.getInt(rs.getColumnIndexForName(AVStorage.Video.Media.ID));
                    Uri uri = Uri.appendEncodedPathToUri(externalDataAbilityUri,"" + mediaId);
                    fileDescriptor = helper.openFile(uri, "r");
                    return fileDescriptor;
                }
            }
        } catch (Exception e) {
            System.out.println("rs read error!");
            e.printStackTrace();
        } finally {
            if (rs != null) {
                rs.close();
            }
        }
        return fileDescriptor;
    }

    /**
     * 擷取媒體檔案儲存的外部存儲公共目錄的fd,為了寫,插入
     * @param context
     * @param type
     * @param name
     * @return
     */
    public static FileDescriptor getPublicFdForInsert(Context context, 
                                                                       MediaType type, String name) {
        DataAbilityHelper helper = DataAbilityHelper.creator(context);
        Uri externalDataAbilityUri = null;
        ValuesBucket vb = new ValuesBucket();
        switch (type){
            case VIDEO:
                externalDataAbilityUri = AVStorage.Video.Media.EXTERNAL_DATA_ABILITY_URI;
                vb.putString(AVStorage.Video.Media.DISPLAY_NAME,name);
                break;
            case IMAGE:
                externalDataAbilityUri = AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI;
                vb.putString(AVStorage.Images.Media.DISPLAY_NAME,name);
                break;
            case DOWNLOADS:
                externalDataAbilityUri = AVStorage.Downloads.EXTERNAL_DATA_ABILITY_URI;
                vb.putString(AVStorage.Downloads.DISPLAY_NAME,name);
                break;
            case AUDIO:
                externalDataAbilityUri = AVStorage.Audio.Media.EXTERNAL_DATA_ABILITY_URI;//這裡uri對應目錄
                vb.putString(AVStorage.Audio.Media.DISPLAY_NAME,name);
                break;
        }
        FileOutputStream outputStream = null;
        FileDescriptor fd = null;
        try {
            int id = helper.insert(externalDataAbilityUri, vb);
            System.out.println("insert rs:" + id);
            Uri uri = Uri.appendEncodedPathToUri(externalDataAbilityUri, ""+id); //這個uri對應檔案
            outputStream = (FileOutputStream) helper.obtainOutputStream(uri);
            fd = outputStream.getFD();
            return fd;
        } catch (Exception e) {
            System.out.println(" helper.insert error!");
            e.printStackTrace();
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return fd;
    }
}
           

視訊處理核心工具類:

public class MediaHandler {
    public static boolean combineTwoVideos(Context context,
                                        FileDescriptor audioFd,
                                        int audioStartTime,
                                        int audioEndTime,
                                        FileDescriptor videoFd,
                                        int videoStartTime,
                                        int videoEndTime,
                                        FileDescriptor outFd) {

        if(audioFd == null || videoFd==null){
            System.out.println("=======audioSrcFd或者framesSrcFd檔案沒讀到!");
            return false;
        }

        Extractor audioExtractor = new Extractor();
        int mainAudioExtractorTrackIndex = -1; //(提供音頻的)視訊的音頻軌道号
        int mainAudioMuxerTrackIndex = -1; //合成後的視訊的音頻軌
        int mainAudioMaxInputSize = 0; //能擷取的音頻的最大值

        Extractor frameExtractor = new Extractor();
        int frameExtractorTrackIndex = -1; //(提供畫面的)視訊的視訊軌
        int frameMuxerTrackIndex = -1; //合成後的視訊的視訊軌
        int frameMaxInputSize = 0; //能擷取的視訊的最大值
        int frameRate = 0; //視訊的幀率
        long frameDuration = 0;

        Muxer muxer = null; //用于合成音頻與視訊

        try {
            muxer = new Muxer(outFd,Muxer.MediaFileFormat.FORMAT_MPEG4);

            //音軌資訊
            audioExtractor.setSource(new Source(audioFd)); //設定視訊源
            int audioTrackCount = audioExtractor.getTotalStreams(); //擷取資料源的軌道數
            //在此循環軌道數,目的是找到我們想要的音頻軌
            for (int i = 0; i < audioTrackCount; i++) {
                Format format = audioExtractor.getStreamFormat(i);//得到指定索引的記錄格式
                String mimeType = format.getStringValue(Format.MIME);
                System.out.println("=======mimeType:"+mimeType);
                if (mimeType.startsWith("audio/")) { //找到音軌
                    mainAudioExtractorTrackIndex = i;
                    mainAudioMuxerTrackIndex = muxer.appendTrack(format); //将音軌添加到Muxer,并傳回新的軌道
                    mainAudioMaxInputSize = format.getIntValue(Format.MAX_INPUT_SIZE);
                    //得到能擷取的有關音頻的最大值
                }
            }

            //圖像資訊
            frameExtractor.setSource(new Source(videoFd)); //設定視訊源
            int trackCount = frameExtractor.getTotalStreams();//擷取資料源的軌道數
            //在此循環軌道數,目的是找到我們想要的視訊軌
            for (int i = 0; i < trackCount; i++) {
                Format vedioFormat = frameExtractor.getStreamFormat(i);
                String vedioMimeType = vedioFormat.getStringValue(Format.MIME);
                if (vedioMimeType.startsWith("video/")) { //找到視訊軌
                    frameExtractorTrackIndex = i;
                    frameMuxerTrackIndex = muxer.appendTrack(vedioFormat); //将視訊軌添加到Muxer,并傳回新的軌道
                    frameMaxInputSize = vedioFormat.getIntValue(Format.MAX_INPUT_SIZE);
                    frameRate = vedioFormat.getIntValue(Format.FRAME_RATE); //擷取視訊的幀率
                    frameDuration = vedioFormat.getLongValue(Format.DURATION); //擷取視訊時長
                }
            }

            muxer.start(); //開始合成
            audioExtractor.specifyStream(mainAudioExtractorTrackIndex); //将提供音頻的視訊選擇到音軌上
            BufferInfo bufferInfo = new BufferInfo();
            ByteBuffer audioByteBuffer = ByteBuffer.allocate(mainAudioMaxInputSize);
            while (true) {
                int readSampleSize = audioExtractor.readBuffer(audioByteBuffer, 0);
                //檢索目前編碼的樣本并将其存儲在位元組緩沖區中
                long audioSampleTime = audioExtractor.getFrameTimestamp(); //擷取目前展示樣本的時間(機關毫秒)
                if (readSampleSize < 0) { //如果沒有可擷取的樣本則退出循環
                    audioExtractor.unspecifyStream(mainAudioExtractorTrackIndex);
                    break;
                }

                if (audioSampleTime < audioStartTime*1000) { //如果樣本時間小于我們想要的開始時間就快進
                    System.out.println("audioSampleTime < audioStartTime:"
                                       + audioSampleTime +","+ audioStartTime*1000);
                    audioExtractor.next(); //推進到下一個樣本,類似快進
                    continue;
                }
                //如果樣本時間大于結束時間,就退出循環
                if (audioSampleTime > audioEndTime*1000) {
                    System.out.println("audioSampleTime > audioEndTime:" + audioSampleTime 
                                       +","+ audioEndTime*1000);
                    audioExtractor.unspecifyStream(mainAudioExtractorTrackIndex);
                    break;
                }


                //設定樣本編碼資訊
                bufferInfo.size = readSampleSize;
                bufferInfo.offset = 0;
                bufferInfo.bufferType = audioExtractor.getFrameType();
                bufferInfo.timeStamp = audioSampleTime - audioStartTime*1000;

                muxer.writeBuffer(mainAudioMuxerTrackIndex, audioByteBuffer, bufferInfo); //将樣本寫入
                audioExtractor.next(); //推進到下一個樣本,類似快進
                System.out.println("=======正在合成音頻...");
            }

            frameExtractor.specifyStream(frameExtractorTrackIndex); //将提供視訊圖像的視訊選擇到視訊軌上
            BufferInfo vedioBufferInfo = new BufferInfo();
            ByteBuffer videoByteBuffer = ByteBuffer.allocate(frameMaxInputSize);
            while (true) {
                int readSampleSize = frameExtractor.readBuffer(videoByteBuffer, 0);
                //檢索目前編碼的樣本并将其存儲在位元組緩沖區中
                if (readSampleSize < 0) { //如果沒有可擷取的樣本則退出循環
                    frameExtractor.unspecifyStream(frameExtractorTrackIndex);
                    break;
                }

                long videoSampleTime = frameExtractor.getFrameTimestamp(); //擷取目前展示樣本的時間(機關毫秒)
                if (videoSampleTime < videoStartTime*1000) { //如果樣本時間小于我們想要的開始時間就快進
                    System.out.println("videoSampleTime < videoStartTime:" 
                                       + videoSampleTime +","+ videoStartTime*1000);
                    frameExtractor.next(); //推進到下一個樣本,類似快進
                    continue;
                }
                //如果樣本時間大于結束時間,就退出循環
                if (videoSampleTime > videoEndTime*1000) {
                    System.out.println("videoSampleTime > videoEndTime:" 
                                       + videoSampleTime +","+ videoEndTime*1000);
                    frameExtractor.unspecifyStream(mainAudioExtractorTrackIndex);
                    break;
                }
                //設定樣本編碼資訊
                vedioBufferInfo.size = readSampleSize;
                vedioBufferInfo.offset = 0;
                vedioBufferInfo.bufferType = frameExtractor.getFrameType();
                vedioBufferInfo.timeStamp += 1000 * 1000 / frameRate; //每幀的時間是 1/frameRate秒,需轉成微秒

                muxer.writeBuffer(frameMuxerTrackIndex, videoByteBuffer, vedioBufferInfo); //将樣本寫入
                frameExtractor.next(); //推進到下一個樣本,類似快進
                System.out.println("=======正在合成視訊...");
            }
            System.out.println("=======合成完畢");
            return true;
        }catch (Exception e){
            System.out.println("==========combineTwoVideos出錯了!");
            return false;
        }finally {
            //釋放資源
            audioExtractor.release();
            frameExtractor.release();
            if (muxer != null) {
                muxer.release();
            }
        }
    }
}

           

毫秒轉00:00:00格式字元串工具:

public class DateUtils {
    private static final int ONE_SECONDS_MS = 1000;
    private static final int ONE_MINS_MINUTES = 60;
    private static final int NUMBER = 16;
    private static final String TIME_FORMAT = "%02d";
    private static final String SEMICOLON = ":";

    public static String msToString(int ms) {
        StringBuilder sb = new StringBuilder(NUMBER);
        int seconds = ms / ONE_SECONDS_MS;
        int minutes = seconds / ONE_MINS_MINUTES;
        if (minutes > ONE_MINS_MINUTES) {
            sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, minutes / ONE_MINS_MINUTES));
            sb.append(SEMICOLON);
            sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, minutes % ONE_MINS_MINUTES));
            sb.append(SEMICOLON);
        } else {
            sb.append("00:");
            sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, minutes));
            sb.append(SEMICOLON);
        }

        if (seconds > minutes * ONE_MINS_MINUTES) {
            sb.append(String.format(Locale.ENGLISH, TIME_FORMAT, seconds - minutes * ONE_MINS_MINUTES));
        } else {
            sb.append("00");
        }
        return sb.toString();
    }
}
           

視訊播放器工具:

public class PlayerHandler {
    private Player videoPlayer;
    private Context context;
    private Runnable videoRunnable;
    private boolean isFirstPlay = true;
    private EventHandler handler = new EventHandler(EventRunner.create());
    private PlayerStateInterface playerStateInterface;
    private PlayerCurrentTimeInterface playerCurrentTimeInterface;
    public static interface PlayerCurrentTimeInterface{
        void updateCurrentTime(int currentTime);
    }

    public void setPlayerCurrentTimeInterface(PlayerCurrentTimeInterface pcti){
        this.playerCurrentTimeInterface = pcti;
    }

    private Timer timer = new Timer();
    private TimerTask timerTask = null;

    public PlayerHandler(Context context) {
        this.context = context;
    }

    public synchronized void startPlay(Source source, Surface surface) {
        if(videoPlayer == null){
            videoPlayer = new Player(context);
        }
        videoPlayer.setPlayerCallback(new VideoCallBack());
        System.out.println("======isFirstPlay:"+isFirstPlay);
        if(isFirstPlay){
            videoRunnable = () -> firstPlay(source, surface);
            handler.postTask(videoRunnable);
            isFirstPlay = false;
        }else{
            if(!videoPlayer.isNowPlaying()){
                handler.removeTask(videoRunnable);
                videoRunnable = () -> play();
                handler.postTask(videoRunnable);
            }
        }
        timer.purge();
        if(timerTask==null){
            timerTask = new TimerTask() {
                @Override
                public void run() {
                    playerCurrentTimeInterface.updateCurrentTime(getCurrentTime());
                }
            };
        }
        timer.schedule(timerTask, 0, 1000);
    }

    private void firstPlay(Source source, Surface surface) {
        videoPlayer.setSource(source);
        videoPlayer.setVideoSurface(surface);
        videoPlayer.prepare();
        videoPlayer.play();
    }

    private void play() {
        if (videoPlayer == null) {
            return;
        }
        videoPlayer.play();
    }

    public synchronized void pausePlay() {
        if (videoPlayer == null) {
            return;
        }
        videoPlayer.pause();
        timerTask.cancel();
        timerTask = null;//如果不重新new,會報異常

    }

    public void rewindTo(long ms) {
        if (videoPlayer == null) {
            return;
        }
        System.out.println("ms:" + ms);
        videoPlayer.rewindTo(ms*1000);
    }

    public void release() {
        if (videoPlayer != null) {
            videoPlayer.stop();
            videoPlayer.release();
            videoPlayer = null;
            timer.cancel();
            timer = null;
        }
    }

    public int getCurrentTime(){
        if (videoPlayer == null) {
            return 0;
        }
        return videoPlayer.getCurrentTime();
    }

    public static interface PlayerStateInterface {
        void updateTotalTime(int totalTime);
    }

    public void setPlayerStateInterface(PlayerStateInterface psi){
        this.playerStateInterface = psi;
    }





    private class VideoCallBack implements Player.IPlayerCallback{

        @Override
        public void onPrepared() {
            playerStateInterface.updateTotalTime(videoPlayer.getDuration());
        }

        @Override
        public void onMessage(int i, int i1) {

        }

        @Override
        public void onError(int i, int i1) {

        }

        @Override
        public void onResolutionChanged(int i, int i1) {

        }

        @Override
        public void onPlayBackComplete() {

        }

        @Override
        public void onRewindToComplete() {
        }

        @Override
        public void onBufferingChange(int i) {

        }

        @Override
        public void onNewTimedMetaData(Player.MediaTimedMetaData mediaTimedMetaData) {

        }

        @Override
        public void onMediaTimeIncontinuity(Player.MediaTimeInfo mediaTimeInfo) {

        }
    }

}

           

業務邏輯實作:

MainAbilitySlice:從外部存儲公共目錄裡讀取視訊檔案,調整到分了合成頁面或剪切頁面:

public class MainAbilitySlice extends AbilitySlice {
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_main);
        initComponents();
    }

    private void initComponents() {
        Component goCutBtn = findComponentById(ResourceTable.Id_go_cut_btn);
        goCutBtn.setClickedListener(this::goCutBtnFunc);
        Component goSeparateBtn = findComponentById(ResourceTable.Id_go_separate_btn);
        goSeparateBtn.setClickedListener(this::goSeparateBtnFunc);
    }

    private void goSeparateBtnFunc(Component component) {
        Intent intent = new Intent();
        Operation build = new Intent.OperationBuilder()
                .withBundleName(getBundleName())
                .withDeviceId("")
                .withAbilityName(SeparateAbility.class.getName())
                .build();
        intent.setParam("separateFiles",new String[]{"audioFile.mp4","videoFile.mp4"});
        intent.setOperation(build);
        startAbility(intent);
    }

    private void goCutBtnFunc(Component component) {
       //這裡可以做的更靈活一定,做一個檔案清單讀取sd卡上的視訊檔案,選擇要處理的拿出來它的檔案名
        Intent intent = new Intent();
        Operation build = new Intent.OperationBuilder()
                .withBundleName(getBundleName())
                .withDeviceId("")
                .withAbilityName(CutAbility.class.getName())
                .build();
        intent.setParam("cutFile","cut.mp4");
        intent.setOperation(build);
        startAbility(intent);
    }

    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }
}
           

視訊分離合成的邏輯:

public class SeparateAbilitySlice extends AbilitySlice {
    private String[] separateFiles;
    private FileDescriptor audioFd;
    private FileDescriptor videoFd;
    private SurfaceProvider audioSurfaceProvider;
    private Surface audioSurface;
    private SurfaceProvider videoSurfaceProvider;
    private Surface videoSurface;
    private PlayerHandler audioPlayerHandler;
    private PlayerHandler videoPlayerHandler;
    private boolean audioClick = false;
    private boolean videoClick = false;
    private int audioTotalTime;
    private int videoTotalTime;
    private Slider audioProgressBar;
    private Slider videoProgressBar;
    private Button startTime1,startTime2,endTime1,endTime2;
    private Text currentTime1,currentTime2;
    private int startAudioTime1,startVideoTimt2,endAudioTime1,endVideoTime2;
    private Button separateBtn;


    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_separate);
        //初始化元件
        initCompoents();
        //拿到參數和分離合成檔案
        initParam(intent);
        //使用播放器播放視訊
        initSurfaceProvider();
        //設定元件的監聽
        initListeners();
    }

    private void initListeners() {
        audioProgressBar.setValueChangedListener(new Slider.ValueChangedListener() {
            @Override
            public void onProgressUpdated(Slider slider, int value, boolean isB) {
                getUITaskDispatcher().asyncDispatch(() ->
                        currentTime1.setText(DateUtils.msToString(value)));
            }

            @Override
            public void onTouchStart(Slider slider) {

            }

            @Override
            public void onTouchEnd(Slider slider) {
                if (slider.getProgress() == audioTotalTime) {
                    audioPlayerHandler.release();
                } else {
                    audioPlayerHandler.rewindTo(slider.getProgress());
                }
            }
        });

        videoProgressBar.setValueChangedListener(new Slider.ValueChangedListener() {
            @Override
            public void onProgressUpdated(Slider slider, int value, boolean isB) {
                getUITaskDispatcher().asyncDispatch(() ->
                        currentTime2.setText(DateUtils.msToString(value)));
            }

            @Override
            public void onTouchStart(Slider slider) {

            }

            @Override
            public void onTouchEnd(Slider slider) {
                if (slider.getProgress() == videoTotalTime) {
                    videoPlayerHandler.release();
                } else {
                    videoPlayerHandler.rewindTo(slider.getProgress());
                }
            }
        });


        startTime1.setClickedListener(component -> {
            startAudioTime1 = audioPlayerHandler.getCurrentTime();
            startTime1.setText(DateUtils.msToString(startAudioTime1));
        });

        startTime2.setClickedListener(component -> {
            startVideoTimt2 = videoPlayerHandler.getCurrentTime();
            startTime2.setText(DateUtils.msToString(startVideoTimt2));
        });

        endTime1.setClickedListener(component -> {
            endAudioTime1 = audioPlayerHandler.getCurrentTime();
            endTime1.setText(DateUtils.msToString(endAudioTime1));
        });

        endTime2.setClickedListener(component -> {
            endVideoTime2 = videoPlayerHandler.getCurrentTime();
            endTime2.setText(DateUtils.msToString(endVideoTime2));
        });

        separateBtn.setClickedListener(component -> {
            FileDescriptor outFd = StorageFileUtils.getPublicFdForInsert(this
                                       , StorageFileUtils.MediaType.VIDEO, "combineFile.mp4");
            boolean rs = MediaHandler.combineTwoVideos(this,audioFd,startAudioTime1,endAudioTime1
            ,videoFd,startVideoTimt2,endVideoTime2,outFd);
            if (rs){
                getUITaskDispatcher().asyncDispatch(()->{
                   new ToastDialog(this).setText("合成完成").show();
                });
            }
        });
    }

    private void initCompoents() {
        audioProgressBar = (Slider) findComponentById(ResourceTable.Id_progress1);
        videoProgressBar = (Slider) findComponentById(ResourceTable.Id_progress2);
        startTime1 = (Button)findComponentById(ResourceTable.Id_start_time_btn1);
        startTime2 = (Button)findComponentById(ResourceTable.Id_start_time_btn2);
        endTime1 = (Button)findComponentById(ResourceTable.Id_end_time_btn1);
        endTime2 = (Button)findComponentById(ResourceTable.Id_end_time_btn2);
        currentTime1 = (Text) findComponentById(ResourceTable.Id_current_time1);
        currentTime2 = (Text) findComponentById(ResourceTable.Id_current_time2);
        separateBtn = (Button)findComponentById(ResourceTable.Id_separate_btn);
    }


    private void initSurfaceProvider() {
        audioSurfaceProvider = new SurfaceProvider(this);
        audioSurfaceProvider.getSurfaceOps().get().addCallback(new AudioSurfaceCallBack());
        audioSurfaceProvider.pinToZTop(true);
        DirectionalLayout directionalLayout1 =
                (DirectionalLayout) findComponentById(ResourceTable.Id_play_directionalLayout1);
        directionalLayout1.addComponent(audioSurfaceProvider);

        videoSurfaceProvider = new SurfaceProvider(this);
        videoSurfaceProvider.getSurfaceOps().get().addCallback(new VedioSurfaceCallBack());
        videoSurfaceProvider.pinToZTop(true);
        DirectionalLayout directionalLayout2 =
                (DirectionalLayout) findComponentById(ResourceTable.Id_play_directionalLayout2);
        directionalLayout2.addComponent(videoSurfaceProvider);
    }


    private class AudioSurfaceCallBack implements SurfaceOps.Callback{
        @Override
        public void surfaceCreated(SurfaceOps surfaceOps) {
            System.out.println("======audio surfaceCreated");
            if (audioSurfaceProvider.getSurfaceOps().isPresent()) {
                audioSurface = audioSurfaceProvider.getSurfaceOps().get().getSurface();
                initAudioPlayer(audioSurface);

            }
        }
        @Override
        public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {}
        @Override
        public void surfaceDestroyed(SurfaceOps surfaceOps) {}
    }

    private class VedioSurfaceCallBack implements SurfaceOps.Callback{
        @Override
        public void surfaceCreated(SurfaceOps surfaceOps) {
            System.out.println("======video surfaceCreated");
            if (videoSurfaceProvider.getSurfaceOps().isPresent()) {
                videoSurface = videoSurfaceProvider.getSurfaceOps().get().getSurface();
                initVideoPlayer(videoSurface);
            }
        }
        @Override
        public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {}
        @Override
        public void surfaceDestroyed(SurfaceOps surfaceOps) {}
    }

    private void initAudioPlayer(Surface audioSurface) {
        System.out.println("======initAudioPlayer");
        audioPlayerHandler = new PlayerHandler(getApplicationContext());
        audioPlayerHandler.setPlayerStateInterface(new PlayerHandler.PlayerStateInterface(){
            @Override
            public void updateTotalTime(int totalTime) {
                System.out.println("======slice totalTime:" + totalTime);
                audioTotalTime = totalTime;
                getUITaskDispatcher().asyncDispatch(()->{
                    Text time1 = (Text)findComponentById(ResourceTable.Id_end_time1);
                    time1.setText(DateUtils.msToString(totalTime));
                    audioProgressBar.setMaxValue(totalTime);
                });
            }
        });
        audioPlayerHandler.setPlayerCurrentTimeInterface(new PlayerHandler.PlayerCurrentTimeInterface() {
            @Override
            public void updateCurrentTime(int currentTime) {
                getUITaskDispatcher().asyncDispatch(()->{
                    audioProgressBar.setProgressValue(currentTime);
                });
            }
        });
        Source source = new Source(audioFd);
        audioSurfaceProvider.setClickedListener(component -> {
            if(!audioClick){
                audioClick = !audioClick;
                audioPlayerHandler.startPlay(source, audioSurface);
             }else{
                audioClick = !audioClick;
                audioPlayerHandler.pausePlay();
            }
        });
    }

    private void initVideoPlayer(Surface audioSurface) {
        System.out.println("======initVideoPlayer");
        videoPlayerHandler = new PlayerHandler(getApplicationContext());
        videoPlayerHandler.setPlayerStateInterface(new PlayerHandler.PlayerStateInterface(){
            @Override
            public void updateTotalTime(int totalTime) {
                videoTotalTime = totalTime;
                getUITaskDispatcher().asyncDispatch(()->{
                    Text time2 = (Text)findComponentById(ResourceTable.Id_end_time2);
                    time2.setText(DateUtils.msToString(totalTime));
                    videoProgressBar.setMaxValue(totalTime);
                });
            }
        });
        videoPlayerHandler.setPlayerCurrentTimeInterface(new PlayerHandler.PlayerCurrentTimeInterface() {
            @Override
            public void updateCurrentTime(int currentTime) {
                videoProgressBar.setProgressValue(currentTime);
            }
        });
        Source source = new Source(videoFd);
        videoSurfaceProvider.setClickedListener(component -> {
            if(!videoClick){
                videoClick = !videoClick;
                videoPlayerHandler.startPlay(source, videoSurface);
            }else{
                videoClick = !videoClick;
                videoPlayerHandler.pausePlay();
            }
        });
    }

    private void initParam(Intent intent) {
        if(intent != null){
            separateFiles = intent.getStringArrayParam("separateFiles");
            System.out.println("=====" + Arrays.toString(separateFiles));
            if(separateFiles.length == 2){
                audioFd = StorageFileUtils.getPublicFileFdForRead(
                        this,
                        StorageFileUtils.MediaType.VIDEO,
                        separateFiles[0]
                );
                videoFd = StorageFileUtils.getPublicFileFdForRead(
                        this,
                        StorageFileUtils.MediaType.VIDEO,
                        separateFiles[1]
                );
            }
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        audioPlayerHandler.release();
        videoSurfaceProvider.release();
    }

    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }

}
           

視訊截取邏輯代碼:

public class CutAbilitySlice extends AbilitySlice {
    private Slider videoProgressBar;
    private Button startTime;
    private Button endTime;
    private Button cutBtn;
    private String cutFile;
    private FileDescriptor videoFd;
    private SurfaceProvider videoSurfaceProvider;
    private Surface videoSurface;
    private PlayerHandler videoPlayerHandler;
    private int videoTotalTime;
    private boolean videoClick;
    private Text currentTimeText;
    private int startVideoTime;
    private int endVideoTime;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_cut);
        //初始化元件
        initCompoents();
        //拿到參數和分離合成檔案
        initParam(intent);
        //使用播放器播放視訊
        initSurfaceProvider();
        //設定元件的監聽
        initListeners();
    }

    private void initCompoents() {
        videoProgressBar = (Slider) findComponentById(ResourceTable.Id_progress);
        startTime = (Button)findComponentById(ResourceTable.Id_start_time_btn);
        endTime = (Button)findComponentById(ResourceTable.Id_end_time_btn);
        cutBtn = (Button)findComponentById(ResourceTable.Id_cut_btn);
        currentTimeText = (Text) findComponentById(ResourceTable.Id_current_time);
    }

    private void initParam(Intent intent) {
        if(intent != null){
            cutFile = intent.getStringParam("cutFile");
            videoFd = StorageFileUtils.getPublicFileFdForRead(
                    this,
                    StorageFileUtils.MediaType.VIDEO,
                    cutFile
            );
        }
    }

    private void initSurfaceProvider() {
        videoSurfaceProvider = new SurfaceProvider(this);
        videoSurfaceProvider.getSurfaceOps().get().addCallback(new VedioSurfaceCallBack());
        videoSurfaceProvider.pinToZTop(true);
        DirectionalLayout directionalLayout =
                (DirectionalLayout) findComponentById(ResourceTable.Id_cut_play_directionalLayout);
        directionalLayout.addComponent(videoSurfaceProvider);
    }

    private class VedioSurfaceCallBack implements SurfaceOps.Callback{
        @Override
        public void surfaceCreated(SurfaceOps surfaceOps) {
            System.out.println("======video surfaceCreated");
            if (videoSurfaceProvider.getSurfaceOps().isPresent()) {
                videoSurface = videoSurfaceProvider.getSurfaceOps().get().getSurface();
                initVideoPlayer(videoSurface);
            }
        }
        @Override
        public void surfaceChanged(SurfaceOps surfaceOps, int i, int i1, int i2) {}
        @Override
        public void surfaceDestroyed(SurfaceOps surfaceOps) {}
    }

    private void initVideoPlayer(Surface audioSurface) {
        System.out.println("======initVideoPlayer");
        videoPlayerHandler = new PlayerHandler(getApplicationContext());
        videoPlayerHandler.setPlayerStateInterface(new PlayerHandler.PlayerStateInterface(){
            @Override
            public void updateTotalTime(int totalTime) {
                videoTotalTime = totalTime;
                getUITaskDispatcher().asyncDispatch(()->{
                    Text time = (Text)findComponentById(ResourceTable.Id_end_time);
                    time.setText(DateUtils.msToString(totalTime));
                    videoProgressBar.setMaxValue(totalTime);
                });
            }
        });
        videoPlayerHandler.setPlayerCurrentTimeInterface(new PlayerHandler.PlayerCurrentTimeInterface() {
            @Override
            public void updateCurrentTime(int currentTime) {
                videoProgressBar.setProgressValue(currentTime);
            }
        });
        Source source = new Source(videoFd);
        videoSurfaceProvider.setClickedListener(component -> {
            if(!videoClick){
                videoClick = !videoClick;
                videoPlayerHandler.startPlay(source, videoSurface);
            }else{
                videoClick = !videoClick;
                videoPlayerHandler.pausePlay();
            }
        });
    }

    private void initListeners() {
        videoProgressBar.setValueChangedListener(new Slider.ValueChangedListener() {
            @Override
            public void onProgressUpdated(Slider slider, int value, boolean isB) {
                getUITaskDispatcher().asyncDispatch(() ->
                        currentTimeText.setText(DateUtils.msToString(value)));
            }

            @Override
            public void onTouchStart(Slider slider) {

            }

            @Override
            public void onTouchEnd(Slider slider) {
                if (slider.getProgress() == videoTotalTime) {
                    videoPlayerHandler.release();
                } else {
                    videoPlayerHandler.rewindTo(slider.getProgress());
                }
            }
        });




        startTime.setClickedListener(component -> {
            startVideoTime = videoPlayerHandler.getCurrentTime();
            startTime.setText(DateUtils.msToString(startVideoTime));
        });



        endTime.setClickedListener(component -> {
            endVideoTime = videoPlayerHandler.getCurrentTime();
            endTime.setText(DateUtils.msToString(endVideoTime));
        });

        cutBtn.setClickedListener(component -> {
            FileDescriptor outFd = StorageFileUtils.getPublicFdForInsert(this
                                               , StorageFileUtils.MediaType.VIDEO, "cutFileRs.mp4");
            boolean rs = MediaHandler.combineTwoVideos(this,videoFd,startVideoTime,endVideoTime
                    ,videoFd,startVideoTime,endVideoTime,outFd);
            if (rs){
                getUITaskDispatcher().asyncDispatch(()->{
                    new ToastDialog(this).setText("剪切完成").show();
                });
            }
        });
    }

    @Override
    protected void onStop() {
        super.onStop();
        videoSurfaceProvider.release();
    }


    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }
}
           

附件連結:https://harmonyos.51cto.com/resource/1664

想了解更多關于鴻蒙的内容,請通路:

51CTO和華為官方戰略合作共建的鴻蒙技術社群

https://harmonyos.51cto.com/#bkwz