利用簡單的IO操作實現M3U8檔案之間的合併

instr發表於2023-05-16

先上程式碼:

  1    @SneakyThrows  //合併操作,最終檔案不包含結束標識,方便多次合併
  2     private static void mergeM3U8File(String source, String target) {
  3         
  4         //讀取target
  5         List<String> sl = new ArrayList<>();
  6         try (BufferedReader reader = new BufferedReader(new FileReader(source))) {
  7             String line;
  8             while ((line = reader.readLine()) != null) {
  9                 sl.add(line);
 10             }
 11         }
 12         //讀取source
 13         List<String> tl = new ArrayList<>();
 14         try (BufferedReader reader = new BufferedReader(new FileReader(target))) {
 15             String line;
 16             while ((line = reader.readLine()) != null) {
 17                 tl.add(line);
 18             }
 19         }
 20         //合併且統一ts檔名
 21         String filename = target.replace(dir,"");
 22         filename = filename.replace(".m3u8","");
 23         Long order = 0l;
 24         if (tl.size() <= 0) {
 25             for (String s : sl) {
 26                 if(s.startsWith("#")) tl.add(s);
 27                 else {
 28                     tl.add(filename + order + ".ts");
 29                     log.info("轉存檔案 {}{} 存在狀態 {}", dir, s, Files.exists(Paths.get(dir + s)));
 30                     Files.copy(Paths.get(dir + s), Paths.get(dir + filename + order + ".ts"));
 31                     Files.delete(Paths.get(dir + s));
 32                     order++;
 33                 }
 34             }
 35         }
 36         else {
 37             //刪除結束段落
 38             if(tl.get(tl.size()-1).startsWith("#EXT-X-ENDLIST")) {
 39                 tl.remove(tl.size() - 1);
 40             }
 41             //獲取檔案序號(dir+filename+order.ts)
 42             String s = tl.get(tl.size() - 1);
 43             s = s.replace(".ts","");
 44             s = s.replace(filename,"");
 45             order = Long.parseLong(s);
 46             //sl處理 獲取頭部標識的下標並移除頭部
 47             int i = 0;
 48             for (i = 0; i < sl.size(); i++) {
 49                 if (sl.get(i).startsWith("#EXTINF")) break;
 50             }
 51             for (int j = 0; j < i; j++) {
 52                 sl.remove(0);
 53             }
 54             sl.remove(sl.size() - 1);
 55             tl.add("#EXT-X-DISCONTINUITY");
 56             //sl檔案內容寫入tl
 57             for (int j = 0; j < sl.size(); j++) {
 58                 String s1 = sl.get(j);
 59                 if(s1.startsWith("#EXTINF")) //#EXTINF
 60                     tl.add(s1);
 61                 else//按規則寫入檔案索引
 62                 {
 63                     order++;
 64                     tl.add(filename + order + ".ts");
 65                     log.info("轉存檔案 {}{} 存在狀態 {}",dir,s1,Files.exists(Paths.get(dir + s1)));
 66                     Files.copy(Paths.get(dir + s1), Paths.get(dir + filename + order + ".ts"));
 67                     Files.delete(Paths.get(dir + s1));
 68                 }
 69             }
 70         }
 71         //生成新的buffer
 72         StringBuffer buffer = new StringBuffer();
 73         for (String t : tl) {
 74             buffer.append(t).append(System.lineSeparator());
 75         }
 76         //寫入檔案
 77         try (BufferedWriter writer = new BufferedWriter(new FileWriter(target))) {
 78             writer.write(buffer.toString());
 79         }
 80         //清除source
 81         Files.delete(Paths.get(source));
 82     }
 83 
 84     @SneakyThrows //給m3u8檔案新增結束標識
 85     private static void endM3u8(String m3u8) {
 86         List<String> sl = new ArrayList<>();
 87         try (BufferedReader reader = new BufferedReader(new FileReader(m3u8))) {
 88             String line;
 89             while ((line = reader.readLine()) != null) {
 90                 sl.add(line);
 91             }
 92         }
 93         sl.add("#EXT-X-ENDLIST");
 94         //生成新的buffer
 95         StringBuffer buffer = new StringBuffer();
 96         for (String t : sl) {
 97             buffer.append(t).append(System.lineSeparator());
 98         }
 99         //寫入檔案
100         try (BufferedWriter writer = new BufferedWriter(new FileWriter(m3u8))) {
101             writer.write(buffer.toString());
102         }
103 
104     }

關於m3u8檔案的說明:

- #EXTM3U:檔案頭,標識這是一個M3U8檔案。
- #EXT-X-VERSION:表示M3U8的版本號。
- #EXT-X-TARGETDURATION:表示每個分段的最長時間(以秒為單位)。
- #EXT-X-MEDIA-SEQUENCE:表示播放列表中的第一個分段的編號。
- #EXTINF:表示當前分段的播放時間長度(以秒為單位)和URI。
- #EXT-X-ENDLIST:表示播放列表結束。

- #EXT-X-STREAM-INF:表示變換位元速率影片流中的音影片屬性。
- #EXT-X-DISCONTINUITY:表示兩個分段之間的不連續性。
- #EXT-X-PROGRAM-DATE-TIME:表示當前分段的播放時間點。
- #EXT-X-BYTERANGE:表示分段的位元組範圍。

合併程式碼中的m3u8檔案的格式:

 * #EXTM3U
* #EXT-X-VERSION:3
* #EXT-X-TARGETDURATION:2
* #EXT-X-MEDIA-SEQUENCE:0
* #EXT-X-PLAYLIST-TYPE:EVENT
* #EXTINF:1.441000,
* 2052636967-12640237988249000.ts
* #EXTINF:1.848000,
* 2052636967-12640237988249001.ts
* #EXT-X-ENDLIST

關於程式碼中的一些說明:

1、由於m3u8檔案中的ts檔案索引規則一般是  m3u8檔名+索引號,因此在合併檔案之前需要先統一索引檔名;

2、由於m3u8檔案之間是相互獨立的,所以要在寫入外部m3u8索引檔案之前加入 #EXT-X-DISCONTINUITY 標識,否則播放器會在播放完第一部分的m3u8檔案之後停止播放;

3、關於m3u8檔案的結束行 #EXT-X-ENDLIST 問題,大部分播放器在未識別到結束標識且已經讀取到檔案最後一行索引時,會不斷的請求m3u8檔案,獲取新資源,可以透過這個特新來實現直播效果;

合併後的檔案內容:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:EVENT
# EXTINF: 1.441000.
2052636967-12640237988249000.ts
# EXTINF: 1.848000.
2052636967-12640237988249001.ts
#EXT-X-DISCONTINUITY
# EXTINF: 1.741000.
2052636967-12640237988249002.ts
# EXTINF: 1.148000.
2052636967-12640237988249003.ts
#EXT-X-ENDLIST


相關文章