網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
簡(jiǎn)介
需要在視頻播放時(shí),同步顯示字幕,市面上主流的字幕文件一般為SRT文件,一般流程為:從服務(wù)器請(qǐng)求一個(gè)url地址,此為zip字幕壓縮文件,一般需要請(qǐng)求url,下載zip文件,解析zip文件得到字幕srt文件,最后進(jìn)行顯示
下載
請(qǐng)求就不在此多言了,每個(gè)服務(wù)器請(qǐng)求體,返回題各異,沒(méi)有參考價(jià)值。
下載zip文件我們需要在本地創(chuàng)建一個(gè)本地文件夾用來(lái)存儲(chǔ)此文件。
創(chuàng)建文件夾
public String createDirectory(String name) { File dir = new File(BaseApplication.getContext().getCacheDir(), name); File file = BaseApplication.getContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); if (file != null) { dir = new File(file, name); } if (!dir.exists()) { dir.mkdirs(); } return dir.toString(); }
文件下載
文件下載使用的開(kāi)源框架Filedownloader
?implementation 'com.liulishuo.filedownloader:library:1.7.7'//download
參數(shù)一:下載地址
參數(shù)二:文件存儲(chǔ)地址
參數(shù)三:回調(diào)
從外部傳入需要的下載參數(shù),然后通過(guò)callback回調(diào)出去,進(jìn)行頁(yè)面更新操作
public void StartDownloadFile(String url,String path,FileDownloaderCallback callback){ FileDownloader.getImpl().create(url).setPath(path,true).setListener(new FileDownloadListener() { @Override protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) { callback.start(); } @Override protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) { } @Override protected void completed(BaseDownloadTask task) { callback.completed(task.getPath()); } @Override protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) { } @Override protected void error(BaseDownloadTask task, Throwable e) { callback.failed(); } @Override protected void warn(BaseDownloadTask task) { } }).start(); }
下載調(diào)用以及文件解析調(diào)用
此處建立文件下載文件夾以及解析完成的文件夾地址,然后通過(guò)調(diào)用上述filedownloader文件下載回調(diào),然后在下載完成的回調(diào)中進(jìn)行文件zip解析
public void download(String url,String title,DownloadResultCallback callback){ String input = "InputDirectory"; String output = "OutputDirectory"; String inPath = FileUtils.getInstance().createDirectory(input); String outPath = FileUtils.getInstance().createDirectory(output); String sub = FileUtils.getInstance().createFile(inPath,"subTitleFile"+ File.separator); DownloadUtils.getInstance().StartDownloadFile(url, sub, new DownloadUtils.FileDownloaderCallback() { @Override public void start() { callback.downloadStart(); } @Override public void completed(String inputPath) { callback.downloadSuccess(); String path = inputPath + "/" + title +".zip"; try { ZipUtils.UnZipFolder(path,outPath); callback.resolveSuccess(); } catch (Exception e) { callback.resolveFailed(e.getMessage()); e.printStackTrace(); } } @Override public void failed() { callback.downloadFailed(); } }); }
解析
ZIP文件解析
此處被上述調(diào)用,用于zip文件解析
參數(shù)一:需要被解析的zip文件地址
參數(shù)二:輸出文件夾地址
public class ZipUtils { public static void UnZipFolder(String zipFileString, String outPathString)throws Exception { java.util.zip.ZipInputStream inZip = new java.util.zip.ZipInputStream(new java.io.FileInputStream(zipFileString)); java.util.zip.ZipEntry zipEntry; String szName = ""; while ((zipEntry = inZip.getNextEntry()) != null) { szName = zipEntry.getName(); if (zipEntry.isDirectory()) { // get the folder name of the widget szName = szName.substring(0, szName.length() - 1); java.io.File folder = new java.io.File(outPathString + java.io.File.separator + szName); folder.mkdirs(); } else { java.io.File file = new java.io.File(outPathString + java.io.File.separator + szName); file.createNewFile(); // get the output stream of the file java.io.FileOutputStream out = new java.io.FileOutputStream(file); int len; byte[] buffer = new byte[1024]; // read (len) bytes into buffer while ((len = inZip.read(buffer)) != -1) { // write (len) byte from buffer at the position 0 out.write(buffer, 0, len); out.flush(); } out.close(); } }//end of while inZip.close(); }//end o }
外部引用
參數(shù)一:下載url地址
參數(shù)二:存儲(chǔ)文件夾名稱
參數(shù)三:callback
上述zip文件下載以及zip文件解析為一個(gè)封裝類,此處為在外部傳入?yún)?shù),通過(guò)回調(diào)進(jìn)行頁(yè)面更新,然后在resolveSuccess()方法中進(jìn)行異步操作(此方法代表zip文件被下載成功并且已被成功解析)
private void download(){ if (titleBeanList == null || titleBeanList.size() == 0)return; if (curSubTitlePos == 0)return; DownloadUtils.getInstance().download(titleBeanList.get(curSubTitlePos).getSub(), titleBeanList.get(curSubTitlePos).getT_name(), new DownloadUtils.DownloadResultCallback() { @Override public void downloadStart() { Log.d(TAG,"download start"); } @Override public void downloadSuccess() { Log.d(TAG,"download success"); } @Override public void downloadFailed() { Log.d(TAG,"download fail"); } @Override public void resolveSuccess() { Log.d(TAG,"resolve success"); handler.sendEmptyMessage(6); } @Override public void resolveFailed(String failMsg) { Log.d(TAG,"resolve error:"+failMsg); } }); }
轉(zhuǎn)換
轉(zhuǎn)換SRT字幕文件
通過(guò)將本地的SRT字幕文件轉(zhuǎn)為相對(duì)應(yīng)集合實(shí)體數(shù)據(jù),具體實(shí)體類型根據(jù)SRT文件內(nèi)容而定
public static List<SrtEntity> getSrtInfoList(String srtPath){ List<SrtEntity> srtList = new ArrayList<>(); try { InputStreamReader read = new InputStreamReader(new FileInputStream(srtPath), "utf-8"); BufferedReader bufferedReader = new BufferedReader(read); String textLine; CursorStatus cursorStatus = CursorStatus.NONE; SrtEntity entity = null; while ((textLine = bufferedReader.readLine()) != null){ textLine = textLine.trim(); if (cursorStatus == CursorStatus.NONE) { if (textLine.isEmpty()) { continue; } if (!isNumeric(textLine)){ continue; } // New cue entity = new SrtEntity(); // First textLine is the cue number try { entity.setNumber(Integer.parseInt(textLine)); } catch (NumberFormatException e) { } cursorStatus = CursorStatus.CUE_ID; continue; } // Second textLine defines the start and end time codes // 00:01:21,456 --> 00:01:23,417 if (cursorStatus == CursorStatus.CUE_ID) { if (!textLine.substring(13, 16).equals("-->")) { throw new Exception(String.format( "Timecode textLine is badly formated: %s", textLine)); } entity.setBg(parseTimeCode(textLine.substring(0, 12))); entity.setEd(parseTimeCode(textLine.substring(17))); cursorStatus = CursorStatus.CUE_TIMECODE; continue; } // Following lines are the cue lines if (!textLine.isEmpty() && ( cursorStatus == CursorStatus.CUE_TIMECODE || cursorStatus == CursorStatus.CUE_TEXT)) { entity.addLine(textLine); cursorStatus = CursorStatus.CUE_TEXT; continue; } if (cursorStatus == CursorStatus.CUE_TIMECODE && textLine.isEmpty()) { entity.addLine(textLine); cursorStatus = CursorStatus.CUE_TEXT; continue; } if (cursorStatus == CursorStatus.CUE_TEXT && textLine.isEmpty()) { // End of cue srtList.add(entity); entity = null; cursorStatus = CursorStatus.NONE; continue; } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); } catch (FileNotFoundException e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); } catch (Exception e) { e.printStackTrace(); Log.e(TAG, e.getMessage()); } return srtList; }
獲取SRT文件list實(shí)體數(shù)據(jù)
通過(guò)以上步驟之后,即可將SRT文件轉(zhuǎn)為相對(duì)應(yīng)的list實(shí)體數(shù)據(jù),最后與視頻聲音進(jìn)行同步即可達(dá)到字幕與聲音同步的效果
String outPath = FileUtils.getInstance().createDirectory("OutputDirectory"); String path = outPath +"/" +titleBeanList.get(curSubTitlePos).getT_name(); srtEntityList.addAll(SrtParser.getSrtInfoList(path));
顯示
字幕顯示
然后通過(guò)獲取字幕文件的片段的開(kāi)始時(shí)間與結(jié)束時(shí)間,若當(dāng)前視頻的播放進(jìn)度在此范圍之內(nèi),即顯示字幕,否則繼續(xù)尋找;
private void showSubTitle(){ if (srtEntityList == null || srtEntityList.size() == 0)return; for (int i = curSubTitleNum; i < srtEntityList.size(); i++) { long start = srtEntityList.get(i).getBg().getTime()+subtitleSpeed; long end = srtEntityList.get(i).getEd().getTime()+subtitleSpeed; if (curProgress >= start && curProgress <= end){ /** * 字幕與進(jìn)度相匹配*/ binding.VideoPlay.setSubTitle(srtEntityList.get(i).content.getText()); curSubTitleNum = i; break; } } }
若用戶往前拖動(dòng)視頻進(jìn)度條,則將字幕文件片段下標(biāo)置為0,從頭開(kāi)始匹配
if (currentPosition - curProgress < 0){ //seek -- curSubTitleNum = 0; }
原文鏈接:https://blog.csdn.net/News53231323/article/details/126017080
相關(guān)推薦
- 2022-05-08 C#使用Unity實(shí)現(xiàn)IOC_實(shí)用技巧
- 2022-07-19 RPM命令和YUM命令用法
- 2023-04-07 一文詳解如何用GPU來(lái)運(yùn)行Python代碼_python
- 2022-02-13 淺析ARMv8匯編指令adrp和adr_匯編語(yǔ)言
- 2022-04-10 Android中Protobuf的基本使用介紹_Android
- 2022-10-29 微服務(wù)啟動(dòng)報(bào)錯(cuò):No Feign Client for loadBalancing defined.
- 2023-12-19 CentOS和Ubuntu中防火墻相關(guān)命令
- 2022-05-31 如何使用正則表達(dá)式判斷郵箱(以C#為例)_C#教程
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支