1 use { 2 super::{errors::MediaError, ts::Ts}, 3 bytes::BytesMut, 4 std::{collections::VecDeque, fs, fs::File, io::Write}, 5 }; 6 7 pub struct Segment { 8 /*ts duration*/ 9 pub duration: i64, 10 pub discontinuity: bool, 11 /*ts name*/ 12 pub name: String, 13 path: String, 14 pub is_eof: bool, 15 } 16 17 impl Segment { new( duration: i64, discontinuity: bool, name: String, path: String, is_eof: bool, ) -> Self18 pub fn new( 19 duration: i64, 20 discontinuity: bool, 21 name: String, 22 path: String, 23 is_eof: bool, 24 ) -> Self { 25 Self { 26 duration, 27 discontinuity, 28 name, 29 path, 30 is_eof, 31 } 32 } 33 } 34 35 pub struct M3u8 { 36 version: u16, 37 sequence_no: u64, 38 /*What duration should media files be? 39 A duration of 10 seconds of media per file seems to strike a reasonable balance for most broadcast content. 40 http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8*/ 41 duration: i64, 42 /*How many files should be listed in the index file during a continuous, ongoing session? 43 The normal recommendation is 3, but the optimum number may be larger.*/ 44 live_ts_count: usize, 45 46 segments: VecDeque<Segment>, 47 48 m3u8_folder: String, 49 live_m3u8_name: String, 50 51 ts_handler: Ts, 52 53 need_record: bool, 54 vod_m3u8_content: String, 55 vod_m3u8_name: String, 56 } 57 58 impl M3u8 { new( duration: i64, live_ts_count: usize, app_name: String, stream_name: String, need_record: bool, ) -> Self59 pub fn new( 60 duration: i64, 61 live_ts_count: usize, 62 app_name: String, 63 stream_name: String, 64 need_record: bool, 65 ) -> Self { 66 let m3u8_folder = format!("./{app_name}/{stream_name}"); 67 fs::create_dir_all(m3u8_folder.clone()).unwrap(); 68 69 let live_m3u8_name = format!("{stream_name}.m3u8"); 70 let vod_m3u8_name = if need_record { 71 format!("vod_{stream_name}.m3u8") 72 } else { 73 String::default() 74 }; 75 76 let mut m3u8 = Self { 77 version: 3, 78 sequence_no: 0, 79 duration, 80 live_ts_count, 81 segments: VecDeque::new(), 82 m3u8_folder, 83 live_m3u8_name, 84 ts_handler: Ts::new(app_name, stream_name), 85 // record, 86 need_record, 87 vod_m3u8_content: String::default(), 88 vod_m3u8_name, 89 }; 90 91 if need_record { 92 m3u8.vod_m3u8_content = m3u8.generate_m3u8_header(true); 93 } 94 m3u8 95 } 96 add_segment( &mut self, duration: i64, discontinuity: bool, is_eof: bool, ts_data: BytesMut, ) -> Result<(), MediaError>97 pub fn add_segment( 98 &mut self, 99 duration: i64, 100 discontinuity: bool, 101 is_eof: bool, 102 ts_data: BytesMut, 103 ) -> Result<(), MediaError> { 104 let segment_count = self.segments.len(); 105 106 if segment_count >= self.live_ts_count { 107 let segment = self.segments.pop_front().unwrap(); 108 if !self.need_record { 109 self.ts_handler.delete(segment.path); 110 } 111 112 self.sequence_no += 1; 113 } 114 self.duration = std::cmp::max(duration, self.duration); 115 let (ts_name, ts_path) = self.ts_handler.write(ts_data)?; 116 let segment = Segment::new(duration, discontinuity, ts_name, ts_path, is_eof); 117 118 if self.need_record { 119 self.update_vod_m3u8(&segment); 120 } 121 122 self.segments.push_back(segment); 123 124 Ok(()) 125 } 126 clear(&mut self) -> Result<(), MediaError>127 pub fn clear(&mut self) -> Result<(), MediaError> { 128 if self.need_record { 129 let vod_m3u8_path = format!("{}/{}", self.m3u8_folder, self.vod_m3u8_name); 130 let mut file_handler = File::create(vod_m3u8_path).unwrap(); 131 self.vod_m3u8_content += "#EXT-X-ENDLIST\n"; 132 file_handler.write_all(self.vod_m3u8_content.as_bytes())?; 133 } else { 134 for segment in &self.segments { 135 self.ts_handler.delete(segment.path.clone()); 136 } 137 } 138 139 //clear live m3u8 140 let live_m3u8_path = format!("{}/{}", self.m3u8_folder, self.live_m3u8_name); 141 fs::remove_file(live_m3u8_path)?; 142 143 Ok(()) 144 } 145 generate_m3u8_header(&self, is_vod: bool) -> String146 pub fn generate_m3u8_header(&self, is_vod: bool) -> String { 147 let mut m3u8_header = "#EXTM3U\n".to_string(); 148 m3u8_header += format!("#EXT-X-VERSION:{}\n", self.version).as_str(); 149 m3u8_header += format!("#EXT-X-TARGETDURATION:{}\n", (self.duration + 999) / 1000).as_str(); 150 151 if is_vod { 152 m3u8_header += "#EXT-X-MEDIA-SEQUENCE:0\n"; 153 m3u8_header += "#EXT-X-PLAYLIST-TYPE:VOD\n"; 154 m3u8_header += "#EXT-X-ALLOW-CACHE:YES\n"; 155 } else { 156 m3u8_header += format!("#EXT-X-MEDIA-SEQUENCE:{}\n", self.sequence_no).as_str(); 157 } 158 159 m3u8_header 160 } 161 refresh_playlist(&mut self) -> Result<String, MediaError>162 pub fn refresh_playlist(&mut self) -> Result<String, MediaError> { 163 let mut m3u8_content = self.generate_m3u8_header(false); 164 165 for segment in &self.segments { 166 if segment.discontinuity { 167 m3u8_content += "#EXT-X-DISCONTINUITY\n"; 168 } 169 m3u8_content += format!( 170 "#EXTINF:{:.3}\n{}\n", 171 segment.duration as f64 / 1000.0, 172 segment.name 173 ) 174 .as_str(); 175 176 if segment.is_eof { 177 m3u8_content += "#EXT-X-ENDLIST\n"; 178 break; 179 } 180 } 181 182 let m3u8_path = format!("{}/{}", self.m3u8_folder, self.live_m3u8_name); 183 184 let mut file_handler = File::create(m3u8_path).unwrap(); 185 file_handler.write_all(m3u8_content.as_bytes())?; 186 187 Ok(m3u8_content) 188 } 189 update_vod_m3u8(&mut self, segment: &Segment)190 pub fn update_vod_m3u8(&mut self, segment: &Segment) { 191 if segment.discontinuity { 192 self.vod_m3u8_content += "#EXT-X-DISCONTINUITY\n"; 193 } 194 self.vod_m3u8_content += format!( 195 "#EXTINF:{:.3}\n{}\n", 196 segment.duration as f64 / 1000.0, 197 segment.name 198 ) 199 .as_str(); 200 } 201 } 202