Android SRT字幕文件基礎(chǔ)操作講解
簡介
需要在視頻播放時,同步顯示字幕,市面上主流的字幕文件一般為SRT文件,一般流程為:從服務(wù)器請求一個url地址,此為zip字幕壓縮文件,一般需要請求url,下載zip文件,解析zip文件得到字幕srt文件,最后進(jìn)行顯示
下載
請求就不在此多言了,每個服務(wù)器請求體,返回題各異,沒有參考價值。
下載zip文件我們需要在本地創(chuàng)建一個本地文件夾用來存儲此文件。
創(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(); }
文件下載
文件下載使用的開源框架Filedownloader
implementation 'com.liulishuo.filedownloader:library:1.7.7'//download
參數(shù)一:下載地址
參數(shù)二:文件存儲地址
參數(shù)三:回調(diào)
從外部傳入需要的下載參數(shù),然后通過callback回調(diào)出去,進(jìn)行頁面更新操作
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)用
此處建立文件下載文件夾以及解析完成的文件夾地址,然后通過調(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ù)二:存儲文件夾名稱
參數(shù)三:callback
上述zip文件下載以及zip文件解析為一個封裝類,此處為在外部傳入?yún)?shù),通過回調(diào)進(jìn)行頁面更新,然后在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字幕文件
通過將本地的SRT字幕文件轉(zhuǎn)為相對應(yīng)集合實體數(shù)據(jù),具體實體類型根據(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ù)據(jù)
通過以上步驟之后,即可將SRT文件轉(zhuǎn)為相對應(yīng)的list實體數(shù)據(jù),最后與視頻聲音進(jìn)行同步即可達(dá)到字幕與聲音同步的效果
String outPath = FileUtils.getInstance().createDirectory("OutputDirectory"); String path = outPath +"/" +titleBeanList.get(curSubTitlePos).getT_name(); srtEntityList.addAll(SrtParser.getSrtInfoList(path));
顯示
字幕顯示
然后通過獲取字幕文件的片段的開始時間與結(jié)束時間,若當(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; } } }
若用戶往前拖動視頻進(jìn)度條,則將字幕文件片段下標(biāo)置為0,從頭開始匹配
if (currentPosition - curProgress < 0){ //seek -- curSubTitleNum = 0; }
到此這篇關(guān)于Android SRT字幕文件基礎(chǔ)操作講解的文章就介紹到這了,更多相關(guān)Android SRT字幕文件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
剖析Android Activity側(cè)滑返回的實現(xiàn)原理
在很多的App中,都會發(fā)現(xiàn)利用手指滑動事件,進(jìn)行高效且人性化的交互非常有必要,那么它是怎么實現(xiàn)的呢,本文給大家解析實現(xiàn)原理,對Activity側(cè)滑返回實現(xiàn)代碼感興趣的朋友一起看看吧2021-06-06Android 搜索結(jié)果匹配關(guān)鍵字且高亮顯示功能
這篇文章主要介紹了Android 搜索結(jié)果匹配關(guān)鍵字且高亮顯示功能,需要的朋友可以參考下2017-05-05Android 重寫ViewGroup 分析onMeasure()和onLayout()方法
這篇文章主要介紹了Android 重寫ViewGroup 分析onMeasure()和onLayout()方法的相關(guān)資料,需要的朋友可以參考下2017-06-06Android7.0以上Uri轉(zhuǎn)路徑的方法實現(xiàn)(已驗證)
這篇文章主要介紹了Android7.0以上Uri轉(zhuǎn)路徑的方法實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03Android自定義Seekbar滑動條 Pop提示跟隨滑動按鈕滑動
這篇文章主要為大家詳細(xì)介紹了Android自定義Seekbar滑動條,Pop提示跟隨滑動按鈕滑動,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07BroadcastReceiver靜態(tài)注冊案例詳解
這篇文章主要為大家詳細(xì)介紹了BroadcastReceiver靜態(tài)注冊案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08