diff --git a/CHANGELOG.md b/CHANGELOG.md index 7125c060b9..f61c0fc9ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +tag k0.11.0 +-------------------------------- + +- support ass subtitle effects, and adjust position and scale in real time +- support display multiple pgs bitmap subtitle at the same moment, and adjust position and scale in real time +- dropped old subtitle renderer (text->image(Core Graphics)->CVPixelBuffer->Texture) +- subtitle preference move to player from view +- dropped iOS OpenGL renderer +- support http gzip and deflate use headers +- restore ijk dns cache and http event hook +- enable microdvd subtitle decoder +- meta add chapter info + tag k0.10.5 -------------------------------- @@ -10,7 +23,7 @@ tag k0.10.4 - support install third pre-compiled libs - external subtitle support GBK、BIG5-2003 character set - fix subtitle display more bigger bug on non-retina screen using metal -- support render P216,YUV422P16,P416,YUV444P16,AYUV64,YUVA444P16 pixel format directly +- support render P216、YUV422P16、P416、YUV444P16、AYUV64、YUVA444P16 pixel format directly - upgrade ffmpeg to 5.1.4,openssl to 1.1.1w,opus to 1.4,dav1d to 1.3.0,bluray to 1.3.4 tag k0.10.3 diff --git a/IJKMediaPlayerKit.podspec b/IJKMediaPlayerKit.podspec index 3efcf270a9..1bfefb9071 100644 --- a/IJKMediaPlayerKit.podspec +++ b/IJKMediaPlayerKit.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'IJKMediaPlayerKit' - s.version = '0.10.5' + s.version = '0.11.0' s.summary = 'IJKMediaPlayerKit for ios/macOS.' # This description is used to generate tags and improve search results. @@ -37,6 +37,7 @@ TODO: Add long description of the pod here. '${PODS_TARGET_SRCROOT}/shell/build/product/macos/universal/ffmpeg/include', '${PODS_TARGET_SRCROOT}/shell/build/product/macos/universal/bluray/include', '${PODS_TARGET_SRCROOT}/shell/build/product/macos/universal/dvdread/include', + '${PODS_TARGET_SRCROOT}/shell/build/product/macos/universal/ass/include', '${PODS_TARGET_SRCROOT}/ijkmedia' ], 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) IJK_USE_METAL_2=1', @@ -50,6 +51,7 @@ TODO: Add long description of the pod here. '$(inherited)', '${PODS_TARGET_SRCROOT}/shell/build/product/ios/universal/ffmpeg/include', '${PODS_TARGET_SRCROOT}/shell/build/product/ios/universal/dvdread/include', + '${PODS_TARGET_SRCROOT}/shell/build/product/macos/universal/ass/include', '${PODS_TARGET_SRCROOT}/ijkmedia' ], 'EXCLUDED_ARCHS' => 'armv7', @@ -77,7 +79,8 @@ TODO: Add long description of the pod here. s.source_files = 'ijkmedia/ijkplayer/**/*.{h,c,m,cpp}', 'ijkmedia/ijksdl/**/*.{h,c,m,cpp,metal}', - 'ijkmedia/wrapper/apple/*.{h,m}' + 'ijkmedia/wrapper/apple/*.{h,m}', + 'ijkmedia/tools/*.{h,c}' # s.project_header_files = 'ijkmedia/**/*.{h}' s.public_header_files = 'ijkmedia/wrapper/apple/IJKMediaPlayback.h', @@ -90,19 +93,30 @@ TODO: Add long description of the pod here. 'ijkmedia/wrapper/apple/IJKKVOController.h', 'ijkmedia/wrapper/apple/IJKVideoRenderingProtocol.h', 'ijkmedia/wrapper/apple/IJKMediaPlayerKit.h', - 'ijkmedia/wrapper/apple/IJKInternalRenderView.h' + 'ijkmedia/wrapper/apple/IJKInternalRenderView.h', + 'ijkmedia/ijkplayer/ff_subtitle_def.h', + 'ijkmedia/ijksdl/ijksdl_rectangle.h', + 'ijkmedia/tools/*.{h}' s.exclude_files = 'ijkmedia/ijksdl/ijksdl_extra_log.c', 'ijkmedia/ijkplayer/ijkversion.h', 'ijkmedia/ijkplayer/ijkavformat/ijkioandroidio.c', 'ijkmedia/ijkplayer/android/**/*.*', 'ijkmedia/ijksdl/android/**/*.*', + 'ijkmedia/ijksdl/ijksdl_egl.*', + 'ijkmedia/ijksdl/ijksdl_container.*', 'ijkmedia/ijksdl/ffmpeg/ijksdl_vout_overlay_ffmpeg.{h,c}' s.osx.exclude_files = - 'ijkmedia/ijksdl/ijksdl_egl.*', 'ijkmedia/ijksdl/ios/*.*', 'ijkmedia/wrapper/apple/IJKAudioKit.*' - s.ios.exclude_files = 'ijkmedia/ijksdl/mac/*.*' + s.ios.exclude_files = + 'ijkmedia/ijksdl/mac/*.*', + 'ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_macos.{h,m}', + 'ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_fbo_macos.{h,m}', + 'ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_renderer_macos.{h,m}', + 'ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_shader_compiler.{h,m}', + 'ijkmedia/ijksdl/gles2/**/*.*', + 'ijkmedia/ijksdl/ijksdl_gles2.h' s.osx.vendored_libraries = 'shell/build/product/macos/universal/**/*.a' s.ios.vendored_libraries = 'shell/build/product/ios/universal/**/*.a' diff --git a/README.md b/README.md index 9da5d3f89f..c56c3ebf23 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ sudo dpkg-reconfigure dash ``` git clone https://github.com/debugly/ijkplayer.git ijkplayer cd ijkplayer -git checkout -B latest k0.10.5 +git checkout -B latest k0.11.0 cd shell ./init-any.sh macos @@ -135,7 +135,7 @@ open ../../examples/macos/IJKMediaMacDemo.xcworkspace ``` git clone https://github.com/debugly/ijkplayer.git ijkplayer cd ijkplayer -git checkout -B latest k0.10.5 +git checkout -B latest k0.11.0 cd shell ./init-any.sh ios @@ -150,7 +150,7 @@ open ../../examples/macos/IJKMediaDemo.xcworkspace ``` git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android cd ijkplayer-android -git checkout -B latest k0.10.5 +git checkout -B latest k0.11.0 ./init-android.sh diff --git a/android/ijkplayer/build.gradle b/android/ijkplayer/build.gradle index 7904734c84..7d9c141745 100644 --- a/android/ijkplayer/build.gradle +++ b/android/ijkplayer/build.gradle @@ -26,8 +26,8 @@ ext { targetSdkVersion = 25 - versionCode = 1000500 - versionName = "0.10.5" + versionCode = 1100000 + versionName = "0.11.0" } wrapper { diff --git a/android/ijkplayer/gradle.properties b/android/ijkplayer/gradle.properties index 0e4517cad4..96ca849e51 100644 --- a/android/ijkplayer/gradle.properties +++ b/android/ijkplayer/gradle.properties @@ -17,8 +17,8 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -VERSION_NAME=0.10.5 -VERSION_CODE=1000500 +VERSION_NAME=0.11.0 +VERSION_CODE=1100000 GROUP=tv.danmaku.ijk.media # http://central.sonatype.org/pages/requirements.html diff --git a/android/ijkplayer/ijkplayer-example/build.gradle b/android/ijkplayer/ijkplayer-example/build.gradle index 0542a1a476..b3fea7fca2 100644 --- a/android/ijkplayer/ijkplayer-example/build.gradle +++ b/android/ijkplayer/ijkplayer-example/build.gradle @@ -54,18 +54,18 @@ dependencies { all64Compile project(':ijkplayer-x86') all64Compile project(':ijkplayer-x86_64') - // compile 'tv.danmaku.ijk.media:ijkplayer-java:0.10.5' - // compile 'tv.danmaku.ijk.media:ijkplayer-exo:0.10.5' + // compile 'tv.danmaku.ijk.media:ijkplayer-java:0.11.0' + // compile 'tv.danmaku.ijk.media:ijkplayer-exo:0.11.0' - // all32Compile 'tv.danmaku.ijk.media:ijkplayer-armv5:0.10.5' - // all32Compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.10.5' - // all32Compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.10.5' + // all32Compile 'tv.danmaku.ijk.media:ijkplayer-armv5:0.11.0' + // all32Compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.11.0' + // all32Compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.11.0' - // all64Compile 'tv.danmaku.ijk.media:ijkplayer-armv5:0.10.5' - // all64Compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.10.5' - // all64Compile 'tv.danmaku.ijk.media:ijkplayer-arm64:0.10.5' - // all64Compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.10.5' - // all64Compile 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.10.5' + // all64Compile 'tv.danmaku.ijk.media:ijkplayer-armv5:0.11.0' + // all64Compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.11.0' + // all64Compile 'tv.danmaku.ijk.media:ijkplayer-arm64:0.11.0' + // all64Compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.11.0' + // all64Compile 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.11.0' // armv5Compile project(':player-armv5') // armv7aCompile project(':player-armv7a') diff --git a/android/ijkplayer/ijkplayer-exo/build.gradle b/android/ijkplayer/ijkplayer-exo/build.gradle index aa2902363c..1570c0f38e 100644 --- a/android/ijkplayer/ijkplayer-exo/build.gradle +++ b/android/ijkplayer/ijkplayer-exo/build.gradle @@ -28,7 +28,7 @@ dependencies { compile 'com.google.android.exoplayer:exoplayer:r1.5.11' compile project(':ijkplayer-java') - // compile 'tv.danmaku.ijk.media:ijkplayer-java:0.10.5' + // compile 'tv.danmaku.ijk.media:ijkplayer-java:0.11.0' } gradle.startParameter.taskNames.each { task -> diff --git a/doc/ass-blend.md b/doc/ass-blend.md new file mode 100644 index 0000000000..7e0b349c07 --- /dev/null +++ b/doc/ass-blend.md @@ -0,0 +1,129 @@ +## ass 字幕渲染 + +以下是解决 ass字幕渲染时颜色存在偏差问题的备忘。 + +### 颜色混合原理 + +将要画上去的颜色称为“源颜色”,把原来的颜色称为“目标颜色”; +视频画面就是目标颜色,字幕就是源颜色。 + +定义如下: + +``` +源颜色 (Rs,Gs,Bs,As),源因子 (Sr,Sg,Sb,Sa) +目标颜色 (Rd,Gd,Bd,Ad),目标因子 (Dr,Dg,Db,Da) +颜色混合基础公式: +(Rs*Sr+Rd*Dr, Gs*Sg+Gd*Dg, Bs*Sb+Bd*Db, As*Sa+Ad*Da) +``` + +令: + +``` +Sr=Sg=Sb=Sa +Dr=Dg=Db=Da +``` + +则源公式简化为: + +``` +(Rs*Sa+Rd*Da, Gs*Sa+Gd*Da, Bs*Sa+Bd*Da, As*Sa+Ad*Da) +``` + +再令 `Da = 1-Sa` 可推导出: + +`(Rs*Sa+Rd*(1-Sa), Gs*Sa+Gd**(1-Sa), Bs*Sa+Bd**(1-Sa), As*Sa+Ad**(1-Sa))` + +可根据这个公式编写 ass layer 合成的逻辑: + +``` +static void draw_ass_rgba(unsigned char *src, int src_w, int src_h, + int src_stride, unsigned char *dst, size_t dst_stride, + int dst_x, int dst_y, uint32_t color) +{ + const unsigned int sr = (color >> 24) & 0xff; + const unsigned int sg = (color >> 16) & 0xff; + const unsigned int sb = (color >> 8) & 0xff; + const unsigned int _sa = 0xff - (color & 0xff); + + #define COLOR_BLEND(_sa,_sc,_dc) ((_sc * _sa + _dc * (65025 - _sa)) >> 16 & 0xFF) + + for (int y = 0; y < src_h; y++) { + uint32_t *dstrow = (uint32_t *) dst; + for (int x = 0; x < src_w; x++) { + const uint32_t sa = _sa * src[x]; + + uint32_t dstpix = dstrow[x]; + uint32_t dstr = dstpix & 0xFF; + uint32_t dstg = (dstpix >> 8) & 0xFF; + uint32_t dstb = (dstpix >> 16) & 0xFF; + uint32_t dsta = (dstpix >> 24) & 0xFF; + + dstr = COLOR_BLEND(sa, sr, dstr); + dstg = COLOR_BLEND(sa, sg, dstg); + dstb = COLOR_BLEND(sa, sb, dstb); + dsta = COLOR_BLEND(sa, 255, dsta); + + dstrow[x] = dstr | (dstg << 8) | (dstb << 16) | (dsta << 24); + } + dst += dst_stride; + src += src_stride; + } + #undef COLOR_BLEND +} + +static void blend_single(FFSubtitleBuffer * frame, ASS_Image *img, int layer) +{ + if (img->w == 0 || img->h == 0) + return; + //printf("blend %d rect:{%d,%d}{%d,%d}\n", layer, img->dst_x, img->dst_y, img->w, img->h); + unsigned char *dst = frame->data; + dst += img->dst_y * frame->stride + img->dst_x * 4; + draw_ass_rgba(img->bitmap, img->w, img->h, img->stride, dst, frame->stride, img->dst_x, img->dst_y, img->color); +} + +static void blend(FFSubtitleBuffer * frame, ASS_Image *img) +{ + int cnt = 0; + while (img) { + ++cnt; + blend_single(frame, img, cnt); + img = img->next; + } +} +``` + +以上是 ass image 混合到一张大的 rgba bitmap 的过程。 + +### 渲染 rgba bitmap + +字幕的渲染和视频是分开的,分别有一套纹理上传和渲染的过程,然后做混合。这是由于在集成 libass 之前字幕功能就已经实现了,通过 Core Graphic API 将 文本转成一张刚好装下内容的图片,而 ass 的渲染逻辑则是将多层透明通道的图层混合到一张和视频大小一样的大图上。 + +此处省去纹理上传的相关逻辑。接入 ass 后渲染总是不对,于是将渲染前的 rgba 保存成本地图片查看颜色是正常的,最终定位到是 OpenGl blend 方式的问题! + + + +老版本的 OpenGL 不支持对每个颜色分量单独设置混合因子,而是所有分量统一使用一个因子,实际上使用的就是上面简化后的公式, +glBlendFunc 方法的第一个参数是上面公式的 Sa 参数,第二个参数是上面公式的 Da 参数。 + +OpenGL 里可用的常量: + +``` +GL_ZERO: 表示使用0.0作为因子,实际上相当于不使用这种颜色参与混合运算。 +GL_ONE: 表示使用1.0作为因子,实际上相当于完全的使用了这种颜色参与混合运算。 +GL_SRC_ALPHA:表示使用源颜色的alpha值来作为因子。 +GL_DST_ALPHA:表示使用目标颜色的alpha值来作为因子。 +GL_ONE_MINUS_SRC_ALPHA:表示用1.0减去源颜色的alpha值来作为因子。 +GL_ONE_MINUS_DST_ALPHA:表示用1.0减去目标颜色的alpha值来作为因子。 +``` + +解决 ass 字幕渲染问题,需要设置正确的混合模式: + +glEnable(GL_BLEND); +//之前使用的是 GL_SRC_ALPHA,而不是 GL_ONE +glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + +改成 GL_ONE 之后,原本的 Core Graphic API 生成的图片仍旧正常显示。 + +## end + +到此问题解决,你能想明白为什么 ass 字幕的图片需要使用 GL_ONE 这个源因子吗? diff --git a/examples/ios/IJKMediaDemo.xcodeproj/project.pbxproj b/examples/ios/IJKMediaDemo.xcodeproj/project.pbxproj index 0349834a83..791889e7b9 100644 --- a/examples/ios/IJKMediaDemo.xcodeproj/project.pbxproj +++ b/examples/ios/IJKMediaDemo.xcodeproj/project.pbxproj @@ -478,6 +478,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; TARGETED_DEVICE_FAMILY = 1; VALID_ARCHS = "arm64 x86_64"; WRAPPER_EXTENSION = app; @@ -507,6 +510,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; TARGETED_DEVICE_FAMILY = 1; VALID_ARCHS = "arm64 x86_64"; WRAPPER_EXTENSION = app; diff --git a/examples/ios/IJKMediaDemo/IJKMoviePlayerViewController.m b/examples/ios/IJKMediaDemo/IJKMoviePlayerViewController.m index 09ed01ae31..39ab0c7c62 100644 --- a/examples/ios/IJKMediaDemo/IJKMoviePlayerViewController.m +++ b/examples/ios/IJKMediaDemo/IJKMoviePlayerViewController.m @@ -113,9 +113,9 @@ - (void)viewDidLoad self.player.scalingMode = IJKMPMovieScalingModeAspectFit; self.player.shouldAutoplay = YES; - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; + IJKSDLSubtitlePreference p = self.player.subtitlePreference; p.color = 16776960; - self.player.view.subtitlePreference = p; + self.player.subtitlePreference = p; self.view.autoresizesSubviews = YES; [self.view addSubview:self.player.view]; [self.view addSubview:self.mediaControl]; diff --git a/examples/ios/Podfile.lock b/examples/ios/Podfile.lock index 7046c3a212..a9af1bb57f 100644 --- a/examples/ios/Podfile.lock +++ b/examples/ios/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - IJKMediaPlayerKit (0.10.5) + - IJKMediaPlayerKit (0.11.0) DEPENDENCIES: - IJKMediaPlayerKit (from `../../`) @@ -9,8 +9,8 @@ EXTERNAL SOURCES: :path: "../../" SPEC CHECKSUMS: - IJKMediaPlayerKit: 65b39f55bda0e03320d1fe72ac7824c99f230468 + IJKMediaPlayerKit: b70eef8376bfc532e26524ae8c55112e5ae5821a PODFILE CHECKSUM: cd296d63e0523ffeac3abedb895edb14934a2d48 -COCOAPODS: 1.15.0 +COCOAPODS: 1.15.2 diff --git a/examples/macos/IJKMediaDemo/2e0fb226-d7c3-4672-a4bc.m3u8 b/examples/macos/IJKMediaDemo/2e0fb226-d7c3-4672-a4bc.m3u8 deleted file mode 100644 index a3c5f0087a..0000000000 --- a/examples/macos/IJKMediaDemo/2e0fb226-d7c3-4672-a4bc.m3u8 +++ /dev/null @@ -1,6 +0,0 @@ -#EXTM3U -#EXT-X-TARGETDURATION:30 -#EXT-X-VERSION:3 -#EXTINF:14.08, -https://data.vod.itc.cn/m3u8?&start=86.76&end=100.84&ba=0.0&bv=0.0&k=hWODtfkIlBNO5Bv4WOcmhVsIww74kBd4UhHaUIiotP3Rp1C8YXUyYbS0m1cWJNsvm12ZD6SotNcgmeUqFo7NF2sWh6swm1UoTbcWhWS0pviNF2CNmcIKOLsSCA&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqpxWjWlvzSXBqLPBomxWXfXIWDo2gTPcWOb4WOdAoOe9RYAsWY44fhvt5DdXWhW9WLoGWOvAoMwsfDWOvm8I9kIWr&sig=0Nr9R0YwpJ5qshUp2I6j-cs4xxlWijCL -#EXT-X-ENDLIST diff --git a/examples/macos/IJKMediaDemo/5003509-693880-1.m3u8 b/examples/macos/IJKMediaDemo/5003509-693880-1.m3u8 deleted file mode 100644 index 49d067875e..0000000000 --- a/examples/macos/IJKMediaDemo/5003509-693880-1.m3u8 +++ /dev/null @@ -1,70 +0,0 @@ -#EXTM3U -#EXT-X-TARGETDURATION:30 -#EXT-X-VERSION:3 -#EXTINF:7, -http://data.vod.itc.cn/m3u8?&start=0&end=6.8&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:12.799, -http://data.vod.itc.cn/m3u8?&start=6.8&end=19.599&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:11.601, -http://data.vod.itc.cn/m3u8?&start=19.599&end=31.2&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:10.92, -http://data.vod.itc.cn/m3u8?&start=31.2&end=42.12&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:11.759, -http://data.vod.itc.cn/m3u8?&start=42.12&end=53.879&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:10.761, -http://data.vod.itc.cn/m3u8?&start=53.879&end=64.64&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:11.72, -http://data.vod.itc.cn/m3u8?&start=64.64&end=76.36&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:10.559, -http://data.vod.itc.cn/m3u8?&start=76.36&end=86.919&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:10.96, -http://data.vod.itc.cn/m3u8?&start=86.919&end=97.879&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:13.4, -http://data.vod.itc.cn/m3u8?&start=97.879&end=111.279&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:10.64, -http://data.vod.itc.cn/m3u8?&start=111.279&end=121.919&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:10.36, -http://data.vod.itc.cn/m3u8?&start=121.919&end=132.279&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:6.241, -http://data.vod.itc.cn/m3u8?&start=132.279&end=138.52&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:11.8, -http://data.vod.itc.cn/m3u8?&start=138.52&end=150.32&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:14.52, -http://data.vod.itc.cn/m3u8?&start=150.32&end=164.84&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:6.76, -http://data.vod.itc.cn/m3u8?&start=164.84&end=171.6&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:13.079, -http://data.vod.itc.cn/m3u8?&start=171.6&end=184.679&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:11.36, -http://data.vod.itc.cn/m3u8?&start=184.679&end=196.039&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:10.28, -http://data.vod.itc.cn/m3u8?&start=196.039&end=206.319&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:11.72, -http://data.vod.itc.cn/m3u8?&start=206.319&end=218.039&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:13.601, -http://data.vod.itc.cn/m3u8?&start=218.039&end=231.64&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:6.679, -http://data.vod.itc.cn/m3u8?&start=231.64&end=238.319&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:12.28, -http://data.vod.itc.cn/m3u8?&start=238.319&end=250.599&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:14.201, -http://data.vod.itc.cn/m3u8?&start=250.599&end=264.8&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:7.16, -http://data.vod.itc.cn/m3u8?&start=264.8&end=271.96&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:13.12, -http://data.vod.itc.cn/m3u8?&start=271.96&end=285.08&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:12.639, -http://data.vod.itc.cn/m3u8?&start=285.08&end=297.719&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:9.083, -http://data.vod.itc.cn/m3u8?&start=297.719&end=307.002&ba=0.0&bv=0.0&k=hWODtfkIlBvsfDcsWBNHbA93oJAtoeAigM1ePtvIf36oZDzokxytHrChROdzSPWXZxIWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ewpN1P52FGxNeWWUJMCmmN8NEttGYfz9 -#EXTINF:6.359, -http://data.vod.itc.cn/m3u8?&start=0&end=6.159&ba=307.002&bv=307.002&k=hWODtfkIlBvsWFcXWFc4gV6AqLdCDK61N8CtgTKMq2dMe6dF5m47fFoV0Y2Owm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ZbQR8BL-2GYoO4KVXRt0YdJxuxte9aGY -#EXTINF:13.041, -http://data.vod.itc.cn/m3u8?&start=6.159&end=19.2&ba=307.002&bv=307.002&k=hWODtfkIlBvsWFcXWFc4gV6AqLdCDK61N8CtgTKMq2dMe6dF5m47fFoV0Y2Owm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ZbQR8BL-2GYoO4KVXRt0YdJxuxte9aGY -#EXTINF:12.92, -http://data.vod.itc.cn/m3u8?&start=19.2&end=32.12&ba=307.002&bv=307.002&k=hWODtfkIlBvsWFcXWFc4gV6AqLdCDK61N8CtgTKMq2dMe6dF5m47fFoV0Y2Owm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ZbQR8BL-2GYoO4KVXRt0YdJxuxte9aGY -#EXTINF:12.36, -http://data.vod.itc.cn/m3u8?&start=32.12&end=44.48&ba=307.002&bv=307.002&k=hWODtfkIlBvsWFcXWFc4gV6AqLdCDK61N8CtgTKMq2dMe6dF5m47fFoV0Y2Owm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ZbQR8BL-2GYoO4KVXRt0YdJxuxte9aGY -#EXTINF:10.28, -http://data.vod.itc.cn/m3u8?&start=44.48&end=54.76&ba=307.002&bv=307.002&k=hWODtfkIlBvsWFcXWFc4gV6AqLdCDK61N8CtgTKMq2dMe6dF5m47fFoV0Y2Owm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pwWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=ZbQR8BL-2GYoO4KVXRt0YdJxuxte9aGY -#EXT-X-ENDLIST diff --git a/examples/macos/IJKMediaDemo/5003509-693880-2.webvtt b/examples/macos/IJKMediaDemo/5003509-693880-2.webvtt deleted file mode 100644 index 8a9c30e051..0000000000 --- a/examples/macos/IJKMediaDemo/5003509-693880-2.webvtt +++ /dev/null @@ -1,1166 +0,0 @@ -WEBVTT -X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 - -00:09.580 --> 00:13.220 -《老友记》第一季第一集 莫妮卡的新室友 - -00:54.840 --> 00:58.020 -中央咖啡馆 - -00:56.860 --> 00:58.170 -这没什么好说的 - -00:58.170 --> 01:00.040 -他不过是我的同事 - -01:00.040 --> 01:02.290 -少来了 你们都在约会了 - -01:02.300 --> 01:04.340 -这个男人一定有什么问题 - -01:05.050 --> 01:07.910 -他是驼背吗 戴着假发的驼背 - -01:09.390 --> 01:11.570 -等等 他吃粉笔吗 - -01:12.070 --> 01:13.420 -我只是不想你 - -01:13.420 --> 01:15.090 -重蹈我和卡尔的覆辙 - -01:15.470 --> 01:18.320 -拜托各位 放松点 这甚至不算是约会 - -01:18.320 --> 01:19.900 -只不过是两个人出去吃个饭 - -01:19.900 --> 01:21.630 -而且不会上床 - -01:21.630 --> 01:23.540 -听起来像是和我约会 - -01:25.990 --> 01:27.400 -于是我回到了高中学校 - -01:27.400 --> 01:28.990 -我站在食堂中间 - -01:28.990 --> 01:32.610 -然后发现自己全身赤裸 - -01:32.610 --> 01:34.280 -噢 做过那种梦 - -01:34.280 --> 01:37.530 -然后我低下头 看见那里有一部 - -01:39.020 --> 01:40.450 -大哥大 - -01:41.930 --> 01:44.000 --那话儿变成了 -没错 - -01:44.000 --> 01:45.420 --这种梦倒从未做过 -没有 - -01:45.420 --> 01:49.500 -突然 那电话响起来了 - -01:50.020 --> 01:52.650 -结果是我妈打来的 - -01:53.500 --> 01:56.180 -这非常非常奇怪 - -01:56.530 --> 01:58.700 -因为她从来不打电话给我 - -02:03.180 --> 02:04.770 -嗨 - -02:05.870 --> 02:08.910 -这家伙一张嘴打招呼 我就想自杀 - -02:09.350 --> 02:10.580 -你还好吧 亲爱的 - -02:10.580 --> 02:12.770 -我感觉有人把手伸入我的喉咙 - -02:12.770 --> 02:14.630 -抓住我的小肠 - -02:14.630 --> 02:15.820 -从我嘴里扯出来 - -02:15.830 --> 02:17.010 -然后缠在我脖子上 - -02:17.020 --> 02:18.450 -饼干 - -02:20.470 --> 02:22.360 -卡罗尔今天把她的东西搬走了 - -02:23.100 --> 02:25.240 --我帮你泡杯咖啡 -谢了 - -02:29.760 --> 02:34.090 -不 不要 别再净化我的灵气了 - -02:34.370 --> 02:37.470 -求你了 别碰我的灵气 好吗 - -02:38.350 --> 02:39.720 -我会没事的 好吧 - -02:39.720 --> 02:41.920 -真的 各位 我希望她能幸福 - -02:41.920 --> 02:42.980 --不 你不会 -没错 我不会的 - -02:42.980 --> 02:45.510 -去她的 她甩掉了我 - -02:46.310 --> 02:48.810 -你又不知道她是女同性恋 - -02:52.240 --> 02:54.560 -不知道 行了吧 - -02:54.630 --> 02:58.420 -为何大家都绕着这个话题打转 - -02:58.870 --> 03:01.780 -她都不知道 我怎么会知道 - -03:03.570 --> 03:06.450 -有时我希望自己也是个女同性恋 - -03:08.140 --> 03:10.490 -我大声说出来了么 - -03:11.880 --> 03:13.870 -好了罗斯 瞧 - -03:13.870 --> 03:16.040 -你现在很痛苦 - -03:16.040 --> 03:18.590 -你很生气 很伤心 - -03:18.600 --> 03:20.940 -要不要我给你支一招 - -03:21.010 --> 03:23.090 -脱衣舞俱乐部 - -03:24.590 --> 03:27.140 -别这样 作为光棍儿 爷们起来 - -03:27.140 --> 03:29.330 -我不想单身的 好吗 - -03:29.340 --> 03:32.330 -我只想 再次结婚 - -03:36.780 --> 03:39.960 -而我只想要一百万美金 - -03:41.180 --> 03:42.370 -瑞秋 - -03:42.370 --> 03:45.310 -噢 天啊 莫妮卡 嗨 谢天谢地 - -03:45.310 --> 03:46.990 -我刚去了你家 可你不在 - -03:46.990 --> 03:49.530 -有个拿大锤子的家伙说你可能会在这儿 - -03:49.530 --> 03:51.300 -结果你真的在这儿 你真的在这儿 - -03:51.300 --> 03:54.050 --要来杯咖啡吗 -无咖啡因的 - -03:55.270 --> 03:57.310 -好了 各位 这是瑞秋 - -03:57.770 --> 03:59.570 -另一位林肯高中的幸存者 - -03:59.570 --> 04:01.080 -这就是各位啦 这是钱德勒 - -04:01.080 --> 04:02.880 -菲比 还有乔伊 - -04:02.880 --> 04:04.800 -还记得我哥哥罗斯吗 - -04:04.800 --> 04:06.760 --当然 嗨 -嗨 - -04:15.650 --> 04:16.970 -你是想现在跟我们说说 - -04:16.970 --> 04:20.080 -还是我们再等四位湿漉漉的伴娘过来 - -04:21.510 --> 04:23.510 -噢 上帝 好吧 - -04:23.510 --> 04:25.990 -大概在婚礼前半个小时 - -04:26.390 --> 04:29.190 -我当时在存放礼品的房间里 - -04:29.190 --> 04:31.600 -看着调味汁瓶 - -04:31.600 --> 04:34.730 -那是一件极其美丽的里摩日细瓷调味汁瓶 - -04:34.730 --> 04:35.750 -然后 突然间 - -04:35.750 --> 04:36.650 -有糖吗 - -04:36.650 --> 04:40.410 -我发现我对这个调味汁瓶 - -04:40.410 --> 04:42.760 -比巴瑞更能引发我的兴奋感 - -04:42.790 --> 04:44.730 -然后我真的吓坏了 - -04:44.730 --> 04:46.070 -突然想到 - -04:46.080 --> 04:48.790 -巴瑞真的超像薯头先生 - -04:49.120 --> 04:51.330 -我是说 我总是觉得他眼熟 - -04:51.330 --> 04:52.700 -但是 - -04:55.170 --> 04:57.970 -不管怎么说 我必须离开那里 - -04:57.970 --> 04:58.630 -我开始想 - -04:58.630 --> 05:02.380 -"我干吗这么做 我这么做是为了谁" - -05:02.550 --> 05:04.120 -不管怎么说 我不知该去哪里 - -05:04.120 --> 05:06.200 -而且我知道我们有点疏远了 - -05:06.200 --> 05:08.370 -但是 你是我在这个城市 - -05:08.370 --> 05:09.990 -唯一认识的人 - -05:09.990 --> 05:11.910 -还没有被邀请参加婚礼 - -05:11.910 --> 05:14.530 -噢 我希望这不是个问题 - -05:29.000 --> 05:31.930 -我猜他送她一台管风琴 - -05:31.930 --> 05:33.800 -而她很不喜欢 - -05:35.580 --> 05:38.670 -爸爸 我只是 不能嫁给他 - -05:38.670 --> 05:42.720 -对不起 我只是不爱他 - -05:43.810 --> 05:46.180 -可是对我来说就有关系 - -05:48.670 --> 05:52.320 -噢 她真不应该穿那条裤子 - -05:52.880 --> 05:54.960 -要我说 推她下楼 - -05:54.960 --> 05:59.690 -推她下楼 把推她下楼 把推她下楼 - -06:02.110 --> 06:04.130 -爸 你听我说 - -06:04.450 --> 06:05.730 -这就好像 在我一生中 - -06:05.730 --> 06:08.810 -所有人都一直告诉我"你是一只鞋子 - -06:08.810 --> 06:11.400 -你是鞋子 你是鞋子 你是鞋子 - -06:11.400 --> 06:13.180 -可今天我停下来说 - -06:13.180 --> 06:14.790 -"如果我不想成为一只鞋呢" - -06:14.790 --> 06:19.690 -如果我想当一个包包 或是一顶帽子呢 - -06:20.740 --> 06:22.230 -不 我不是要你给我买帽子 - -06:22.240 --> 06:23.600 -我说我是一顶帽子 - -06:23.600 --> 06:25.730 -这是比喻啦 爸爸 - -06:27.390 --> 06:29.750 -这就是他的问题所在了 - -06:32.590 --> 06:35.460 -听着爸爸 这是我的人生 - -06:35.640 --> 06:39.140 -或许我可以和莫妮卡住在一起 - -06:41.490 --> 06:44.720 -我猜和莫妮卡一起住的人选已然敲定了 - -06:45.380 --> 06:47.880 -也许那是我的决定 - -06:48.780 --> 06:51.280 -也许我不需要你的钱 - -06:51.280 --> 06:53.310 -等等我说也许 - -07:01.450 --> 07:03.400 -深呼吸 - -07:03.400 --> 07:06.460 -就这样试著想些美好平静的事物 - -07:06.480 --> 07:10.460 -玫瑰上的雨滴 小兔子和小猫咪 - -07:10.460 --> 07:15.130 -还有风铃草 雪橇铃 和啥啥的手套 - -07:15.130 --> 07:17.990 -啦啦啦 啥啥和绳子串起的面条 - -07:17.990 --> 07:18.690 -还有一些 - -07:18.690 --> 07:20.360 -我现在好多了 - -07:22.030 --> 07:23.520 -我帮上忙啦 - -07:26.030 --> 07:28.500 -瞧 或许这样反倒最好 - -07:28.500 --> 07:31.300 -你独立了 可以主宰自己的人生 - -07:31.300 --> 07:35.620 -如有任何需求 尽管来找乔伊 - -07:35.620 --> 07:38.120 -我和钱德勒就住在对面 - -07:38.120 --> 07:40.060 -而且他经常不在家 - -07:41.010 --> 07:42.600 -乔伊 别再勾引她了 - -07:42.600 --> 07:44.150 -今天是她结婚的日子 - -07:44.150 --> 07:46.580 -怎么啦 难道还有什么明文规定不成 - -07:48.320 --> 07:51.990 -请不要在这么干了 这声音很烦人 - -07:52.090 --> 07:53.850 -我是保罗 - -07:53.860 --> 07:54.790 -让他进来 - -07:54.790 --> 07:57.440 --保罗是谁 -调酒的那个保罗吗 - -07:57.440 --> 07:58.000 -也许吧 - -07:58.120 --> 08:00.240 -等等 你今晚的"非正式约会"对象 - -08:00.240 --> 08:02.290 -是调酒师保罗 - -08:02.290 --> 08:04.060 --他终于开口约你了吗 -是的 - -08:04.060 --> 08:06.660 -噢 这真是值得永久铭记的时刻 - -08:07.370 --> 08:09.050 -瑞秋 我可以取消 - -08:09.050 --> 08:11.060 -不 你去吧我没事 - -08:11.060 --> 08:14.520 -罗斯 你没事吧你要我留下来吗 - -08:15.350 --> 08:17.110 -那样最好 - -08:18.090 --> 08:20.210 --真的吗 -才怪 去吧 - -08:20.210 --> 08:22.170 -那可是调酒师保罗 - -08:25.680 --> 08:28.440 -嗨 请进 保罗 这是 - -08:29.190 --> 08:31.960 -大家 各位 这是保罗 - -08:31.970 --> 08:32.760 --嘿 保罗 -你好 - -08:32.760 --> 08:34.320 -嗨 调酒师 - -08:34.320 --> 08:37.250 -抱歉 没听清你的名字 保罗 是吗 - -08:37.670 --> 08:39.980 -好了 坐会 马上就好 - -08:44.240 --> 08:47.770 -噢 我刚拔掉四根睫毛 不是好兆头 - -08:49.930 --> 08:52.250 -瑞秋 你 你今晚打算怎么过 - -08:52.250 --> 08:54.540 -这个嘛 本来我应该 - -08:54.540 --> 08:58.070 -在阿鲁巴岛度蜜月 所以 没事做 - -08:59.810 --> 09:02.920 -对哦 你都不能去度蜜月 上帝 - -09:02.920 --> 09:07.830 -不过在阿鲁巴岛的这个时候 说到你的 - -09:08.980 --> 09:10.900 -大蜥蜴 - -09:14.920 --> 09:17.620 -不管怎样如果你今晚不想独处的话 - -09:17.620 --> 09:19.290 -乔伊和钱德勒要到我那儿 - -09:19.290 --> 09:20.780 -帮我组合新家具 - -09:20.780 --> 09:23.660 -是呀 我们特别期待特别兴奋 - -09:24.340 --> 09:25.520 -真的很感谢 - -09:25.520 --> 09:27.660 -不过我今晚想待在这儿 - -09:27.660 --> 09:30.230 --今天真的很漫长 -好吧 当然了 - -09:30.230 --> 09:31.910 -菲比你想来帮忙吗 - -09:31.910 --> 09:34.660 -我倒希望能去帮忙 但我不想去 - -09:41.890 --> 09:46.210 -我应该用一套这样的小蜗杆 - -09:46.210 --> 09:49.420 -将托架装在侧面 - -09:50.250 --> 09:55.190 -但我没看见托架 也没有看见什么蜗杆 - -09:55.190 --> 09:58.210 -而且我的腿麻了 - -10:00.150 --> 10:01.300 -这是什么 - -10:01.300 --> 10:03.960 -我也不知道 - -10:06.220 --> 10:08.400 --书架做好了 -全部完工 - -10:12.230 --> 10:14.890 -这是卡罗尔最爱喝的啤酒 - -10:16.210 --> 10:19.240 -她总是不用易拉罐喝 我该知道 - -10:20.500 --> 10:22.100 -罗斯 我问你一个问题 - -10:22.100 --> 10:26.580 -她得到了家具 音响和很棒的电视机 - -10:26.580 --> 10:28.390 -你得到什么 - -10:28.570 --> 10:29.210 -你们 - -10:29.210 --> 10:30.630 --天啊 -你被坑了 - -10:30.630 --> 10:32.010 -天哪 - -10:32.010 --> 10:36.010 --天哪 -我知道 我是个大白痴 - -10:36.350 --> 10:37.470 -她每周看四五次牙医时 - -10:37.470 --> 10:41.120 -我心里就该有数了 - -10:41.130 --> 10:43.380 -我是说 牙齿能需要多干净呢 - -10:43.530 --> 10:46.100 -我哥也碰到这种事了 他也很纠结 - -10:46.100 --> 10:47.810 -你是怎么熬过来的 - -10:47.810 --> 10:51.590 -你可以尝试 不小心砸烂她贵重的东西 - -10:51.590 --> 10:53.360 --比如她的 -腿 - -10:53.360 --> 10:56.460 -那也是种选择 至于我嘛 - -10:56.460 --> 10:57.930 -我砸烂了她的表 - -10:57.930 --> 11:00.010 -你真的把她的表弄坏了 - -11:00.280 --> 11:03.720 -巴瑞 对不起 真是对不起 - -11:03.720 --> 11:05.290 -你一定认为这件事跟 - -11:05.290 --> 11:07.820 -我那天说你穿着袜子做爱有关 - -11:07.820 --> 11:11.250 -其实不是 是我的原因 我只是 - -11:18.530 --> 11:20.500 -喂 电话又断了 - -11:20.500 --> 11:22.070 -不管怎样 听着 - -11:23.390 --> 11:24.930 -你知道最可怕的是什么 - -11:24.930 --> 11:27.790 -万一人这一辈子只有一个女人怎么办 - -11:27.790 --> 11:29.550 -我是说如果你找了个女人 - -11:29.550 --> 11:31.210 -然后就此过一生 - -11:31.210 --> 11:35.870 -不幸的是 我唯一的女人爱的是 女人 - -11:36.520 --> 11:39.690 -你说什么呢 "一个女人" - -11:40.590 --> 11:44.390 -这就像是在说 你只能吃一种冰淇淋口味 - -11:44.400 --> 11:47.680 -让我告诉你 罗斯 还有很多种口味 - -11:47.680 --> 11:53.210 -有巧克力坚果味 曲奇味还有香草味 - -11:53.360 --> 11:58.120 -还可以和糖条 果仁 或者奶油一起吃 - -11:58.120 --> 12:00.880 -这简直是你一生中最大的喜事 - -12:00.880 --> 12:04.300 -你难道是八岁结的婚吗 - -12:05.720 --> 12:09.450 -欢迎回到现实世界 拿起勺子开挖吧 - -12:09.830 --> 12:13.110 -我真不晓得现在自己是真饿了还是欲火难耐 - -12:13.110 --> 12:14.980 -离我的冰箱远点 - -12:17.920 --> 12:21.130 -自从她甩掉我之后 我就 - -12:21.580 --> 12:25.780 -怎么了 你打算边搅面条边讲吗 - -12:25.780 --> 12:30.140 -不 这该是第五次约会时候才说的事儿 - -12:30.660 --> 12:34.780 -噢 这么说会有第五次约会了 - -12:34.780 --> 12:36.590 -你不想吗 - -12:36.630 --> 12:39.730 -想 我想的 - -12:40.230 --> 12:42.290 -你刚才想说什么 - -12:44.120 --> 12:49.430 -好吧 自从她离开后 - -12:50.680 --> 12:54.170 -我就一直无法过 - -12:54.170 --> 12:55.730 -性生活 - -12:58.340 --> 13:00.140 -天啊 天啊 对不起 - -13:00.140 --> 13:01.610 --真不好意思 -没事 没事 - -13:01.610 --> 13:04.870 -我知道你现在需要的必然不是被人喷一身水 - -13:05.060 --> 13:10.430 --多久了 -两年了 - -13:10.710 --> 13:16.420 -我真高兴你砸烂了她的表 - -13:17.410 --> 13:21.790 -那你还想要第五次约会吗 - -13:23.710 --> 13:27.170 -是的 我希望有 - -13:33.090 --> 13:38.210 -看 乔安妮是爱卡奇的 - -13:38.460 --> 13:40.690 -这就是不同 - -13:42.380 --> 13:43.990 -"拿起勺子" - -13:43.990 --> 13:47.290 -你知道我拿起勺子多久了吗 - -13:47.290 --> 13:50.670 -难道"比利 别逞英雄"这句话对你没意义 - -13:47.290 --> 13:50.670 -[《比利 别逞英雄》 70年代反战歌曲] - -13:52.290 --> 13:54.220 -还有个问题 - -13:54.220 --> 13:58.760 -就算我鼓足了勇气 想约个女人出来 - -13:59.870 --> 14:01.810 -我又能约谁呢 - -14:23.560 --> 14:24.960 -不可思议吧 - -14:24.960 --> 14:27.710 -我这辈子从没泡过咖啡 - -14:27.710 --> 14:30.210 --真不可思议 -恭喜你了 - -14:30.210 --> 14:31.100 -既然你现在正进入角色 - -14:31.100 --> 14:34.530 -有没有想做个煎蛋卷之类的东西 - -14:37.890 --> 14:40.210 -不过事实上我还不太饿 - -14:42.650 --> 14:45.090 --早安 -早安 - -14:46.760 --> 14:48.800 --早安 -早安 保罗 - -14:48.800 --> 14:51.460 --你好保罗 -嗨 保罗是吧 - -14:58.650 --> 15:01.540 --谢谢 太感谢你了 -不要这样 - -15:01.540 --> 15:02.780 -晚点联系 - -15:09.100 --> 15:10.010 -谢谢你 - -15:16.790 --> 15:18.840 -那还不叫真正的约会 - -15:20.190 --> 15:22.670 -那你真正的约会到底要干些什么 - -15:23.950 --> 15:27.150 --闭嘴 把桌子抬回去 -好啦 - -15:27.700 --> 15:29.780 -好了孩子们 我必须上班去了 - -15:29.780 --> 15:31.870 -但如果我不输入那些数字 - -15:32.470 --> 15:34.940 -也没什么关系 - -15:38.220 --> 15:41.200 -你们大伙儿都有工作 - -15:41.200 --> 15:43.100 -对 我们都有工作 - -15:43.100 --> 15:46.030 -这样才有钱买东西 - -15:47.280 --> 15:49.060 -对 我是个演员 - -15:49.190 --> 15:51.210 -你演过什么 我看过吗 - -15:51.210 --> 15:53.750 -难说 大部分范围都挺小的 - -15:53.750 --> 15:55.650 -等下 除非你碰巧看过 - -15:55.660 --> 15:57.620 -《匹诺曹》的重播 - -15:57.630 --> 16:00.760 -"瞧 盖佩多 我是真正的小男孩了" - -15:57.630 --> 16:00.760 -《匹诺曹》的剧中台词 - -16:02.990 --> 16:06.640 --别这么侮辱人 -你说得对 对不起 - -16:07.090 --> 16:09.950 -"我曾是个小木偶 小木偶" - -16:14.090 --> 16:15.710 -今天感觉如何 - -16:15.710 --> 16:18.270 -睡得还好吧 跟巴瑞谈过了吗 - -16:18.270 --> 16:20.370 -我无法停止笑 - -16:20.830 --> 16:21.640 -看得出来 - -16:21.650 --> 16:24.420 -你活像昨晚睡觉时嘴里放了个撑衣架 - -16:30.060 --> 16:33.240 -我知道 他是那么 - -16:33.460 --> 16:35.280 -还记得你和托尼·德马科吗 - -16:35.280 --> 16:36.170 -记得啊 - -16:36.170 --> 16:38.970 -就像那样 那种感觉 - -16:40.240 --> 16:42.440 --哇 你真是有麻烦了 - -16:42.440 --> 16:46.410 -好啦 好啦 我要站起来 去工作 - -16:46.410 --> 16:48.540 -而且一整天都不要想他 - -16:49.180 --> 16:52.030 -或者只是站起来去工作 - -16:52.240 --> 16:54.290 --祝我好运 -为什么 - -16:54.290 --> 16:58.120 -我要去找那个工作什么的东西 - -17:05.510 --> 17:07.050 -嘿 莫妮卡 - -17:07.060 --> 17:08.370 -嘿 弗兰妮 欢迎回来 - -17:08.370 --> 17:09.890 -在佛罗里达过得如何 - -17:09.920 --> 17:12.180 -你做爱了 对不 - -17:13.340 --> 17:15.340 --你怎么知道的 - -17:15.340 --> 17:17.590 -那么 和谁 - -17:17.590 --> 17:19.350 -你认识保罗吧 - -17:19.790 --> 17:21.510 -保罗 那个调酒的 - -17:21.520 --> 17:23.510 -对 我认识保罗 - -17:25.660 --> 17:28.570 -你是说你认识他就像我认识他一样 - -17:28.570 --> 17:31.000 -开玩笑吧 保罗还欠我一个人情呢 - -17:31.000 --> 17:35.600 -遇到我之前他已有两年无法过性生活 - -17:38.280 --> 17:40.550 -显然他是骗你的 - -17:41.130 --> 17:44.150 -为什么 为什么会有人那样做 - -17:44.150 --> 17:46.620 -我想你是想让我们拿出一个 - -17:46.620 --> 17:49.440 -比"想和你上床"更为复杂的回答 - -17:50.380 --> 17:51.890 -问题出在我身上 - -17:52.040 --> 17:54.830 -难道我身上带着某种味儿 - -17:54.830 --> 17:58.810 -只有狗和有严重情感问题的男人才会发觉吗 - -17:58.810 --> 18:00.940 -好啦 过来 把你的脚给我 - -18:08.220 --> 18:10.660 -我只是以为他是个好男人 - -18:12.790 --> 18:15.650 -我真不相信 你不知道那是个谎言 - -18:20.040 --> 18:22.210 --猜猜怎么了 -你找到工作了 - -18:22.210 --> 18:25.380 -开玩笑吗 我什么都不会 - -18:26.820 --> 18:29.120 -今天十二个面试 我都是被嘲笑轰出来的 - -18:29.120 --> 18:30.960 -不过你却异常兴奋 - -18:30.960 --> 18:34.370 -如果你遇见"约翰&大卫"的皮靴打五折 - -18:34.380 --> 18:36.280 -也会跟我一样的 - -18:36.610 --> 18:39.850 -是呀 你多了解我啊 - -18:39.850 --> 18:41.710 -这就是 我不需要工作 - -18:41.710 --> 18:44.720 -不需要父母 一双好皮靴足矣 - -18:46.100 --> 18:47.650 -你怎么付的款 - -18:47.650 --> 18:49.030 -用信用卡 - -18:49.030 --> 18:50.830 -那谁付账单 - -18:51.150 --> 18:53.410 -我爸啊 - -18:55.860 --> 18:57.950 -拜托 你不能靠你爸一辈子 - -18:57.950 --> 19:01.450 -我知道 这就是为什么我要结婚 - -19:02.460 --> 19:05.580 -饶了她吧 第一次独立并不轻松 - -19:05.580 --> 19:06.320 -谢谢 - -19:06.320 --> 19:08.850 -不客气 我记得我第一次来纽约时的情景 - -19:08.850 --> 19:09.900 -当时我十四岁 - -19:09.910 --> 19:12.910 -我妈刚自杀 我继父再度入狱 - -19:12.910 --> 19:15.230 -我来到这里 举目无亲 - -19:15.230 --> 19:17.630 -最后我和一个白化病患者住在一起 - -19:17.630 --> 19:20.040 -他给港务局的人清洗车窗玻璃 - -19:20.040 --> 19:21.900 -后来他也自杀了 - -19:23.370 --> 19:25.000 -然后我成了按摩师 - -19:25.010 --> 19:27.980 -所以相信我 我完全了解你的感受 - -19:31.420 --> 19:34.240 -你想要说的话是 - -19:34.700 --> 19:36.520 -"无论如何" - -19:40.170 --> 19:41.390 -好了准备好了吗 - -19:41.390 --> 19:42.310 --我不这么想 - -19:42.310 --> 19:48.530 -剪掉 剪掉 剪掉 - -19:52.710 --> 19:54.660 -欢迎来到现实世界 - -19:54.660 --> 19:57.340 -它糟透了 但你会喜欢的 - -20:04.340 --> 20:07.870 -好啦 你要睡在沙发上吗 - -20:07.870 --> 20:10.340 -不 我要回家 - -20:10.570 --> 20:12.320 --你没事吧 -没事 - -20:14.050 --> 20:16.760 -莫妮卡 看我在地板上发现了什么 - -20:18.120 --> 20:19.580 -什么 - -20:19.940 --> 20:21.900 -那是保罗的表 - -20:21.900 --> 20:26.510 -放回原地就好了 - -20:26.510 --> 20:30.130 --好吧 晚安各位 -晚安 - -20:41.950 --> 20:42.620 --对不起 -不不 你来 - -20:42.620 --> 20:43.810 -不不 你吃吧 真的 我不想吃 - -20:43.810 --> 20:46.190 --分着吃 -好吧 - -20:50.490 --> 20:51.550 -你大概不知道 - -20:51.550 --> 20:53.850 -我在高中时 曾经 - -20:54.420 --> 20:56.640 -暗恋过你 - -20:57.460 --> 20:58.980 -我知道 - -20:59.170 --> 21:00.880 -你知道 哦 - -21:02.360 --> 21:03.340 -我还以为你一直认为 - -21:03.340 --> 21:06.060 -我是莫妮卡的怪哥哥呢 - -21:06.060 --> 21:06.700 -的确如此 - -21:12.530 --> 21:14.090 -听着 你是不是认为 - -21:14.600 --> 21:16.880 -不要太在意我 - -21:16.880 --> 21:19.070 -脆弱的心灵 - -21:20.020 --> 21:22.200 -但是你觉得我能约你出去吗 - -21:22.200 --> 21:24.010 -偶尔 有可能吗 - -21:25.250 --> 21:28.860 -当然 或许 - -21:29.950 --> 21:38.530 -好 好 或许我会的 - -21:42.890 --> 21:44.450 --晚安 -晚安 - -21:58.470 --> 21:59.910 -再见 - -22:01.970 --> 22:05.180 -等等 你怎么了 - -22:06.560 --> 22:08.840 -我刚刚拿起了勺子 - -22:17.490 --> 22:19.120 -我真不敢相信我的耳朵 - -22:19.120 --> 22:21.040 -我真不敢相信我的耳朵 - -22:21.040 --> 22:21.940 -怎么了 我说你有一个 - -22:21.940 --> 22:23.850 -我说你有一个 - -22:24.390 --> 22:25.910 -你能不能不这么干 - -22:25.910 --> 22:28.420 --噢 我又来了 -是的 - -22:29.710 --> 22:32.550 -有人要咖啡吗 - -22:33.780 --> 22:36.220 -是你冲的 还是你端来的 - -22:36.220 --> 22:38.680 --我端来的 -好吧 给我来杯咖啡 - -22:40.050 --> 22:41.680 -孩子们 我又做新梦了 - -22:42.040 --> 22:43.310 -我在拉斯维加斯 - -22:43.310 --> 22:45.060 -我是丽莎·明妮莉 - -22:43.310 --> 22:45.060 -著名女明星 diff --git a/examples/macos/IJKMediaDemo/5003509-693880-3.m3u8 b/examples/macos/IJKMediaDemo/5003509-693880-3.m3u8 deleted file mode 100644 index dc45452a5f..0000000000 --- a/examples/macos/IJKMediaDemo/5003509-693880-3.m3u8 +++ /dev/null @@ -1,92 +0,0 @@ -#EXTM3U -#EXT-X-TARGETDURATION:30 -#EXT-X-VERSION:3 -#EXTINF:7, -http://data.vod.itc.cn/m3u8?&start=0&end=6.8&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:10, -http://data.vod.itc.cn/m3u8?&start=6.8&end=16.8&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:14.4, -http://data.vod.itc.cn/m3u8?&start=16.8&end=31.2&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:12.44, -http://data.vod.itc.cn/m3u8?&start=31.2&end=43.64&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:10, -http://data.vod.itc.cn/m3u8?&start=43.64&end=53.64&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:11, -http://data.vod.itc.cn/m3u8?&start=53.64&end=64.64&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:11.72, -http://data.vod.itc.cn/m3u8?&start=64.64&end=76.36&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:11.68, -http://data.vod.itc.cn/m3u8?&start=76.36&end=88.04&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:13.96, -http://data.vod.itc.cn/m3u8?&start=88.04&end=102&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:13.199, -http://data.vod.itc.cn/m3u8?&start=102&end=115.199&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:8.161, -http://data.vod.itc.cn/m3u8?&start=115.199&end=123.36&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:11, -http://data.vod.itc.cn/m3u8?&start=123.36&end=134.36&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:14.16, -http://data.vod.itc.cn/m3u8?&start=134.36&end=148.52&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:8.159, -http://data.vod.itc.cn/m3u8?&start=148.52&end=156.679&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:10.601, -http://data.vod.itc.cn/m3u8?&start=156.679&end=167.28&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:13.72, -http://data.vod.itc.cn/m3u8?&start=167.28&end=181&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:13.679, -http://data.vod.itc.cn/m3u8?&start=181&end=194.679&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:11.64, -http://data.vod.itc.cn/m3u8?&start=194.679&end=206.319&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:11.72, -http://data.vod.itc.cn/m3u8?&start=206.319&end=218.039&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:13.601, -http://data.vod.itc.cn/m3u8?&start=218.039&end=231.64&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:6.679, -http://data.vod.itc.cn/m3u8?&start=231.64&end=238.319&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:14.841, -http://data.vod.itc.cn/m3u8?&start=238.319&end=253.16&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:11.64, -http://data.vod.itc.cn/m3u8?&start=253.16&end=264.8&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:5.32, -http://data.vod.itc.cn/m3u8?&start=264.8&end=270.12&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:11, -http://data.vod.itc.cn/m3u8?&start=270.12&end=281.12&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:11.36, -http://data.vod.itc.cn/m3u8?&start=281.12&end=292.48&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:14.322, -http://data.vod.itc.cn/m3u8?&start=292.48&end=307.002&ba=0.0&bv=0.0&k=hWODtfkIlB6XfFcOfJ1HW4wGDesNfTyDge9VfV8lWM9AoVe2POXUyYbS0m1cWJ6S0pbcWhoGyG2t5GWS0TPcgmeUqFo7qLK2ZD64wm1BqVPcgToiuYoGNh2Or&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=xcJNsfvH5ep5p2z1nVeAUdJX47oQiunc -#EXTINF:10, -http://data.vod.itc.cn/m3u8?&start=0&end=9.8&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:5.639, -http://data.vod.itc.cn/m3u8?&start=9.8&end=15.439&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:10.001, -http://data.vod.itc.cn/m3u8?&start=15.439&end=25.44&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:14.079, -http://data.vod.itc.cn/m3u8?&start=25.44&end=39.519&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:13.881, -http://data.vod.itc.cn/m3u8?&start=39.519&end=53.4&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:11.36, -http://data.vod.itc.cn/m3u8?&start=53.4&end=64.76&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:10, -http://data.vod.itc.cn/m3u8?&start=64.76&end=74.76&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:14.4, -http://data.vod.itc.cn/m3u8?&start=74.76&end=89.16&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:12.6, -http://data.vod.itc.cn/m3u8?&start=89.16&end=101.76&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:9, -http://data.vod.itc.cn/m3u8?&start=101.76&end=110.76&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:9.4, -http://data.vod.itc.cn/m3u8?&start=110.76&end=120.16&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:10, -http://data.vod.itc.cn/m3u8?&start=120.16&end=130.16&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:14.8, -http://data.vod.itc.cn/m3u8?&start=130.16&end=144.96&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:11.76, -http://data.vod.itc.cn/m3u8?&start=144.96&end=156.72&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:11.92, -http://data.vod.itc.cn/m3u8?&start=156.72&end=168.64&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:12.8, -http://data.vod.itc.cn/m3u8?&start=168.64&end=181.44&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXTINF:11.599, -http://data.vod.itc.cn/m3u8?&start=181.44&end=193.039&ba=307.002&bv=307.002&k=hWODtfkIlBy45BWOlVfWoVx4ME6D8SkFeOdVRhb28mxXDA4HqM14wmN7ZDvsvm12ZD6SotNcWGXOvm6AZMX2gLsS0psdyF2sWFo70ScAZMEmqVdSotKcWK&a=hWqbzHJUhWqFjfaptUJlzSwdoSwCqmNGopwGqp1VoSrGoSkBhRODOpCU0So70pPWXfXIWDo2gTPcWFvsWGW4f8K9WB6OWO44fB6X5eK4bA89bObtfO6XZYWXPJyFwm8I9kIWr&sig=uq5fw2t1jd8ba2e7snYWNwNISPYMnQWF -#EXT-X-ENDLIST diff --git a/examples/macos/IJKMediaDemo/5003509-693880-5.webvtt b/examples/macos/IJKMediaDemo/5003509-693880-5.webvtt deleted file mode 100644 index 9efe5bf201..0000000000 --- a/examples/macos/IJKMediaDemo/5003509-693880-5.webvtt +++ /dev/null @@ -1,1166 +0,0 @@ -WEBVTT -X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 - -00:09.580 --> 00:13.220 - - -00:54.840 --> 00:58.020 - - -00:56.860 --> 00:58.170 -There's nothing to tell! - -00:58.170 --> 01:00.040 -He's just some guy I work with! - -01:00.040 --> 01:02.290 -C'mon, you're going out with the guy! - -01:02.300 --> 01:04.340 -There's gotta be something wrong with him! - -01:05.050 --> 01:07.910 -So does he have a hump? A hump and a hairpiece? - -01:09.390 --> 01:11.570 -Wait, does he eat chalk? - -01:12.070 --> 01:13.420 -Just, 'cause, I don't want her to go through - -01:13.420 --> 01:15.090 -what I went through with Carl - -01:15.470 --> 01:18.320 -Okay, everybody relax. This is not even a date. - -01:18.320 --> 01:19.900 -It's just two people going out to dinner - -01:19.900 --> 01:21.630 -and not having sex. - -01:21.630 --> 01:23.540 -Sounds like a date to me. - -01:25.990 --> 01:27.400 -Alright, so I'm back in high school, - -01:27.400 --> 01:28.990 -I'm standing in the middle of the cafeteria - -01:28.990 --> 01:32.610 -and I realize I am totally naked. - -01:32.610 --> 01:34.280 -Oh, yeah. Had that dream. - -01:34.280 --> 01:37.530 -Then I look down, and I realize there's a phone - -01:39.020 --> 01:40.450 -there. - -01:41.930 --> 01:44.000 -- Instead of...? - That's right. - -01:44.000 --> 01:45.420 -- Never had that dream. - No. - -01:45.420 --> 01:49.500 -All of a sudden, the phone starts to ring. - -01:50.020 --> 01:52.650 -And it turns out it's my mother, - -01:53.500 --> 01:56.180 -which is very-very weird because- - -01:56.530 --> 01:58.700 -she never calls me! - -02:03.180 --> 02:04.770 -Hi. - -02:05.870 --> 02:08.910 -This guy says hello, I wanna kill myself. - -02:09.350 --> 02:10.580 -Are you okay, sweetie? - -02:10.580 --> 02:12.770 -I just feel like someone reached down my throat - -02:12.770 --> 02:14.630 -grabbed my small intestine, - -02:14.630 --> 02:15.820 -pulled it out of my mouth - -02:15.830 --> 02:17.010 -and tied it around my neck... - -02:17.020 --> 02:18.450 -Cookie? - -02:20.470 --> 02:22.360 -Carol moved her stuff out today - -02:23.100 --> 02:25.240 -- Let me get you some coffee. - Thanks. - -02:29.760 --> 02:34.090 -No, no don't! Stop cleansing my aura! - -02:34.370 --> 02:37.470 -No, just leave my aura alone, okay? - -02:38.350 --> 02:39.720 -I'll be fine, alright? - -02:39.720 --> 02:41.920 -Really, everyone. I hope she'll be very happy. - -02:41.920 --> 02:42.980 -- No you don't. - No I don't, - -02:42.980 --> 02:45.510 -to hell with her, she left me! - -02:46.310 --> 02:48.810 -And you never knew she was a lesbian... - -02:52.240 --> 02:54.560 -No!! Okay?! - -02:54.630 --> 02:58.420 -Why does everyone keep fixating on that? - -02:58.870 --> 03:01.780 -She didn't know, how should I know? - -03:03.570 --> 03:06.450 -Sometimes I wish I was a lesbian... - -03:08.140 --> 03:10.490 -Did I say that out loud? - -03:11.880 --> 03:13.870 -Alright Ross, look. - -03:13.870 --> 03:16.040 -You're feeling a lot of pain right now. - -03:16.040 --> 03:18.590 -You're angry. You're hurting. - -03:18.600 --> 03:20.940 -Can I tell you what the answer is? - -03:21.010 --> 03:23.090 -Strip joint! - -03:24.590 --> 03:27.140 -C'mon, you're single! Have some hormones! - -03:27.140 --> 03:29.330 -I don't want to be single, okay? - -03:29.340 --> 03:32.330 -I just...I just wanna be married again! - -03:36.780 --> 03:39.960 -And I just want a million dollars! - -03:41.180 --> 03:42.370 -Rachel?! - -03:42.370 --> 03:45.310 -Oh,God, Monica, hi! Thank God! - -03:45.310 --> 03:46.990 -I just went to your building and you weren't there - -03:46.990 --> 03:49.530 -and then this guy with a big hammer said you might be here - -03:49.530 --> 03:51.300 -and you are, you are! - -03:51.300 --> 03:54.050 -- Can I get you some coffee? - De-Caff - -03:55.270 --> 03:57.310 -Okay, everybody, this is Rachel, - -03:57.770 --> 03:59.570 -another Lincoln High survivor. - -03:59.570 --> 04:01.080 -This is everybody, this is Chandler, - -04:01.080 --> 04:02.880 -and Phoebe, and Joey, - -04:02.880 --> 04:04.800 -and you remember my brother Ross? - -04:04.800 --> 04:06.760 -- Hi, sure! - Hi. - -04:15.650 --> 04:16.970 -So you wanna tell us now - -04:16.970 --> 04:20.080 -or are we waiting for four wet bridesmaids? - -04:21.510 --> 04:23.510 -Oh, God...well, - -04:23.510 --> 04:25.990 -it started about a half hour before the wedding. - -04:26.390 --> 04:29.190 -I was in the room where we were keeping all the presents, - -04:29.190 --> 04:31.600 -and I was looking at this gravy boat. - -04:31.600 --> 04:34.730 -This really gorgeous Limoges gravy boat. - -04:34.730 --> 04:35.750 -When all of a sudden - -04:35.750 --> 04:36.650 -Sweet 'n' Lo? - -04:36.650 --> 04:40.410 -I realized that I was more turned on - -04:40.410 --> 04:42.760 -by this gravy boat than by Barry! - -04:42.790 --> 04:44.730 -And then I got really freaked out, - -04:44.730 --> 04:46.070 -and that's when it hit me: - -04:46.080 --> 04:48.790 -how much Barry looks like Mr. Potato Head. - -04:49.120 --> 04:51.330 -Y'know, I mean, I always knew looked familiar, - -04:51.330 --> 04:52.700 -but... - -04:55.170 --> 04:57.970 -Anyway, I just had to get out of there, - -04:57.970 --> 04:58.630 -and I started wondering - -04:58.630 --> 05:02.380 -"Why am I doing this, and who am I doing this for?" - -05:02.550 --> 05:04.120 -So anyway I just didn't know where to go, - -05:04.120 --> 05:06.200 -and I know that you and I have kinda drifted apart, - -05:06.200 --> 05:08.370 -but you're the only person I knew - -05:08.370 --> 05:09.990 -who lived here in the city. - -05:09.990 --> 05:11.910 -Who wasn't invited to the wedding. - -05:11.910 --> 05:14.530 -Ooh, I was kinda hoping that wouldn't be an issue... - -05:29.000 --> 05:31.930 -Now I'm guessing that he bought her the big pipe organ, - -05:31.930 --> 05:33.800 -and she's really not happy about it. - -05:35.580 --> 05:38.670 -Daddy, I just... I can't marry him! - -05:38.670 --> 05:42.720 -I'm sorry. I just don't love him. - -05:43.810 --> 05:46.180 -Well, it matters to me! - -05:48.670 --> 05:52.320 -Ooh, she should not be wearing those pants. - -05:52.880 --> 05:54.960 -I say push her down the stairs. - -05:54.960 --> 05:59.690 -Push her down the stairs! Push her down the stairs! Push her down the stairs! - -06:02.110 --> 06:04.130 -C'mon Daddy, listen to me! - -06:04.450 --> 06:05.730 -It's like, it's like, all of my life, - -06:05.730 --> 06:08.810 -everyone has always told me, 'You're a shoe! - -06:08.810 --> 06:11.400 -You're a shoe, you're a shoe, you're a shoe!'. - -06:11.400 --> 06:13.180 -And today I just stopped and I said, - -06:13.180 --> 06:14.790 -'What if I don't wanna be a shoe? - -06:14.790 --> 06:19.690 -What if I wanna be a- a purse, y'know? Or a- or a hat! - -06:20.740 --> 06:22.230 -No, I'm not saying I want you to buy me a hat, - -06:22.240 --> 06:23.600 -I'm saying I am a ha- - -06:23.600 --> 06:25.730 -It's a metaphor, Daddy! - -06:27.390 --> 06:29.750 -You can see where he'd have trouble. - -06:32.590 --> 06:35.460 -Look Daddy, it's my life. - -06:35.640 --> 06:39.140 -Well maybe I'll just stay here with Monica. - -06:41.490 --> 06:44.720 -Well, I guess we've established who's staying here with Monica... - -06:45.380 --> 06:47.880 -Well, maybe that's my decision. - -06:48.780 --> 06:51.280 -Well, maybe I don't need your money. - -06:51.280 --> 06:53.310 -Wait!! Wait, I said maybe!! - -07:01.450 --> 07:03.400 -Just breathe, breathe...that's it. - -07:03.400 --> 07:06.460 -Just try to think of nice calm things... - -07:06.480 --> 07:10.460 -Rain drops on roses and rabbits and kittens - -07:10.460 --> 07:15.130 -bluebells and sleighbells and something with mittens... - -07:15.130 --> 07:17.990 -La la la something and noodles with string. - -07:17.990 --> 07:18.690 -These are a few... - -07:18.690 --> 07:20.360 -I'm all better now. - -07:22.030 --> 07:23.520 -I helped! - -07:26.030 --> 07:28.500 -Okay, look, this is probably for the best, y'know? - -07:28.500 --> 07:31.300 -Independence. Taking control of your life. - -07:31.300 --> 07:35.620 -And hey, you need anything, you can always come to Joey. - -07:35.620 --> 07:38.120 -Me and Chandler live across the hall. - -07:38.120 --> 07:40.060 -And he's away a lot. - -07:41.010 --> 07:42.600 -Joey, stop hitting on her! - -07:42.600 --> 07:44.150 -It's her wedding day! - -07:44.150 --> 07:46.580 -What, like there's a rule or something? - -07:48.320 --> 07:51.990 -Please don't do that again, it's a horrible sound. - -07:52.090 --> 07:53.850 -It's, uh, it's Paul. - -07:53.860 --> 07:54.790 -Buzz him in. - -07:54.790 --> 07:57.440 -- Who's Paul? - Paul the Wine Guy, Paul? - -07:57.440 --> 07:58.000 -Maybe. - -07:58.120 --> 08:00.240 -Wait. Your 'not a real date' tonight - -08:00.240 --> 08:02.290 -is with Paul the Wine Guy? - -08:02.290 --> 08:04.060 -- He finally asked you out? - Yes! - -08:04.060 --> 08:06.660 -Ooh, this is a Dear Diary moment. - -08:07.370 --> 08:09.050 -Rach, wait, I can cancel... - -08:09.050 --> 08:11.060 -Please, no, go, that'd be fine! - -08:11.060 --> 08:14.520 -Are, are you okay? I mean, do you want me to stay? - -08:15.350 --> 08:17.110 -That'd be good... - -08:18.090 --> 08:20.210 -- Really? - No, go on! - -08:20.210 --> 08:22.170 -It's Paul the Wine Guy! - -08:25.680 --> 08:28.440 -Hi, come in! Paul, this is... - -08:29.190 --> 08:31.960 -...everybody, everybody, this is Paul. - -08:31.970 --> 08:32.760 -- Hey! Paul! - Hello! - -08:32.760 --> 08:34.320 -Hi! The Wine Guy! - -08:34.320 --> 08:37.250 -I'm sorry, I didn't catch your name. Paul, was it? - -08:37.670 --> 08:39.980 -Okay, sit down. Two seconds. - -08:44.240 --> 08:47.770 -Ooh, I just pulled out four eyelashes. That can't be good. - -08:49.930 --> 08:52.250 -So Rachel, what're you, uh... what're you up to tonight? - -08:52.250 --> 08:54.540 -Well, I was kinda supposed to be - -08:54.540 --> 08:58.070 -headed for Aruba on my honeymoon, so nothing! - -08:59.810 --> 09:02.920 -Right, you're not even getting your honeymoon, God.. - -09:02.920 --> 09:07.830 -No, no, although, Aruba, this time of year... talk about your - -09:08.980 --> 09:10.900 -big lizards... - -09:14.920 --> 09:17.620 -Anyway, if you don't feel like being alone tonight, - -09:17.620 --> 09:19.290 -Joey and Chandler are coming over to help me - -09:19.290 --> 09:20.780 -put together my new furniture. - -09:20.780 --> 09:23.660 -Yes, and we're very excited about it. - -09:24.340 --> 09:25.520 -Well actually thanks, - -09:25.520 --> 09:27.660 -but I think I'm just gonna hang out here tonight. - -09:27.660 --> 09:30.230 -- It's been kinda a long day. - Okay, sure. - -09:30.230 --> 09:31.910 -Hey, Pheebs, you wanna help? - -09:31.910 --> 09:34.660 -Oh, I wish I could, but I don't want to. - -09:41.890 --> 09:46.210 -I'm supposed to attach a brackety thing to the side things, - -09:46.210 --> 09:49.420 -using a bunch of these little worm guys. - -09:50.250 --> 09:55.190 -I have no brackety thing, I see no whim guys whatsoever - -09:55.190 --> 09:58.210 -and I cannot feel my legs. - -10:00.150 --> 10:01.300 -What's this? - -10:01.300 --> 10:03.960 -I have no idea. - -10:06.220 --> 10:08.400 -- Done with the bookcase! - All finished! - -10:12.230 --> 10:14.890 -This was Carol's favorite beer. - -10:16.210 --> 10:19.240 -She always drank it out of the can, I should have known. - -10:20.500 --> 10:22.100 -Ross, let me ask you a question. - -10:22.100 --> 10:26.580 -She got the furniture, the stereo, the good TV- - -10:26.580 --> 10:28.390 -what did you get? - -10:28.570 --> 10:29.210 -You guys. - -10:29.210 --> 10:30.630 -- Oh, my God. - You got screwed. - -10:30.630 --> 10:32.010 -Oh my God! - -10:32.010 --> 10:36.010 -- Oh my God! - I know, I know, I'm such an idiot. - -10:36.350 --> 10:37.470 -I guess I should have caught on when - -10:37.470 --> 10:41.120 -she started going to the dentist four and five times a week. - -10:41.130 --> 10:43.380 -I mean, how clean can teeth get? - -10:43.530 --> 10:46.100 -My brother's going through that right now, he's such a mess. - -10:46.100 --> 10:47.810 -How did you get through it? - -10:47.810 --> 10:51.590 -Well, you might try accidentally breaking something valuable of hers, - -10:51.590 --> 10:53.360 -- say her... - ...leg? - -10:53.360 --> 10:56.460 -That's one way! Me, I- - -10:56.460 --> 10:57.930 -I went for the watch. - -10:57.930 --> 11:00.010 -You actually broke her watch? - -11:00.280 --> 11:03.720 -Barry, I'm sorry... I am so sorry... - -11:03.720 --> 11:05.290 -I know you probably think that this is all about - -11:05.290 --> 11:07.820 -what I said the other day about you making love with your socks on, - -11:07.820 --> 11:11.250 -but it isn't... it isn't, it's about me, and I just... - -11:18.530 --> 11:20.500 -Hi, machine cut me off again... - -11:20.500 --> 11:22.070 -anyway...look, look, - -11:23.390 --> 11:24.930 -You know what the scariest part is? - -11:24.930 --> 11:27.790 -What if there's only one woman for everybody, y'know? - -11:27.790 --> 11:29.550 -I mean what if you get one woman- - -11:29.550 --> 11:31.210 -and that's it? - -11:31.210 --> 11:35.870 -Unfortunately in my case, there was only one woman- for her... - -11:36.520 --> 11:39.690 -What are you talking about? 'One woman'? - -11:40.590 --> 11:44.390 -That's like saying there's only one flavor of ice cream for you. - -11:44.400 --> 11:47.680 -Lemme tell you something, Ross. There's lots of flavors out there. - -11:47.680 --> 11:53.210 -There's Rocky Road, and Cookie Dough, and Bing! Cherry Vanilla. - -11:53.360 --> 11:58.120 -You could get 'em with Jimmies, or nuts, or whipped cream! - -11:58.120 --> 12:00.880 -This is the best thing that ever happened to you! - -12:00.880 --> 12:04.300 -You got married, you were, like, what, eight? - -12:05.720 --> 12:09.450 -Welcome back to the world! Grab a spoon! - -12:09.830 --> 12:13.110 -I honestly don't know if I'm hungry or horny. - -12:13.110 --> 12:14.980 -Stay out of my freezer! - -12:17.920 --> 12:21.130 -Ever since she walked out on me, I, uh... - -12:21.580 --> 12:25.780 -What?... What, you wanna spell it out with noodles? - -12:25.780 --> 12:30.140 -No, it's, it's more of a fifth date kinda revelation. - -12:30.660 --> 12:34.780 -Oh, so there is gonna be a fifth date? - -12:34.780 --> 12:36.590 -Isn't there? - -12:36.630 --> 12:39.730 -Yeah... yeah, I think there is. - -12:40.230 --> 12:42.290 -What were you gonna say? - -12:44.120 --> 12:49.430 -Well, ever-ev-... ever since she left me, - -12:50.680 --> 12:54.170 -um, I haven't been able to, uh, perform. - -12:54.170 --> 12:55.730 -...Sexually. - -12:58.340 --> 13:00.140 -Oh, God, Oh, God, I am sorry... - -13:00.140 --> 13:01.610 -- I am so sorry... - It's okay... - -13:01.610 --> 13:04.870 -I know being spit on is probably not what you need right now. - -13:05.060 --> 13:10.430 -- Um... how long? - Two years. - -13:10.710 --> 13:16.420 -Wow! I'm-I'm-I'm glad you smashed her watch! - -13:17.410 --> 13:21.790 -So you still think you, um... might want that fifth date? - -13:23.710 --> 13:27.170 -...Yeah. Yeah, I do. - -13:33.090 --> 13:38.210 -Oh...see... but Joanne loved Chachi! - -13:38.460 --> 13:40.690 -That's the difference! - -13:42.380 --> 13:43.990 -Grab a spoon. - -13:43.990 --> 13:47.290 -Do you know how long it's been since I've grabbed a spoon? - -13:47.290 --> 13:50.670 -Do the words 'Billy, don't be a hero' mean anything to you? - -13:47.290 --> 13:50.670 - - -13:52.290 --> 13:54.220 -Y'know, here's the thing. - -13:54.220 --> 13:58.760 -Even if I could get it together enough to- to ask a woman out,... - -13:59.870 --> 14:01.810 -who am I gonna ask? - -14:23.560 --> 14:24.960 -Isn't this amazing? - -14:24.960 --> 14:27.710 -I mean, I have never made coffee before in my entire life. - -14:27.710 --> 14:30.210 -- That is amazing. - Congratulations. - -14:30.210 --> 14:31.100 -While you're on a roll - -14:31.100 --> 14:34.530 -If you feel like you have to make a Western omelet or something.. - -14:37.890 --> 14:40.210 -Although actually I'm really not that hungry... - -14:42.650 --> 14:45.090 -- Morning. - Good morning. - -14:46.760 --> 14:48.800 -- Morning. - Morning, Paul. - -14:48.800 --> 14:51.460 -- Hello, Paul. - Hi, Paul, is it? - -14:58.650 --> 15:01.540 -- Thank you. Thank you so much. - Stop. - -15:01.540 --> 15:02.780 -We'll talk later. - -15:09.100 --> 15:10.010 -Yeah. Thank you. - -15:16.790 --> 15:18.840 -That wasn't a real date?! - -15:20.190 --> 15:22.670 -What the hell do you do on a real date? - -15:23.950 --> 15:27.150 -- Shut up, and put my table back. - Okayyy! - -15:27.700 --> 15:29.780 -All right, kids, I gotta get to work. - -15:29.780 --> 15:31.870 -If I don't input those numbers... - -15:32.470 --> 15:34.940 -it doesn't make much of a difference... - -15:38.220 --> 15:41.200 -So, like, you guys all have jobs? - -15:41.200 --> 15:43.100 -Yeah, we all have jobs. - -15:43.100 --> 15:46.030 -See, that's how we buy stuff. - -15:47.280 --> 15:49.060 -Yeah, I'm an actor. - -15:49.190 --> 15:51.210 -Wow! Would I have seen you in anything? - -15:51.210 --> 15:53.750 -I doubt it. Mostly regional work. - -15:53.750 --> 15:55.650 -Oh wait, wait, unless you happened to catch the - -15:55.660 --> 15:57.620 -Reruns' production of Pinocchio. - -15:57.630 --> 16:00.760 -'Look, Gippetto, I'm a real live boy.' - -15:57.630 --> 16:00.760 - - -16:02.990 --> 16:06.640 -- I will not take this abuse. - You're right, I'm sorry. - -16:07.090 --> 16:09.950 -"Once I was a wooden boy, a little wooden boy..." - -16:14.090 --> 16:15.710 -So how you doing today? - -16:15.710 --> 16:18.270 -Did you sleep okay? Talk to Barry? - -16:18.270 --> 16:20.370 -I can't stop smiling. - -16:20.830 --> 16:21.640 -I can see that. - -16:21.650 --> 16:24.420 -You look like you slept with a hanger in your mouth. - -16:30.060 --> 16:33.240 -I know, he's just so, so... - -16:33.460 --> 16:35.280 -Do you remember you and Tony DeMarco? - -16:35.280 --> 16:36.170 -Oh, yeah. - -16:36.170 --> 16:38.970 -Well, it's like that. With feelings. - -16:40.240 --> 16:42.440 -- Wow,are you in trouble! - -16:42.440 --> 16:46.410 -Okay. Okay. I am just going to get up, go to work - -16:46.410 --> 16:48.540 -and not think about him all day. - -16:49.180 --> 16:52.030 -Or else I'm just gonna get up and go to work. - -16:52.240 --> 16:54.290 -- Oh, look, wish me luck! - What for? - -16:54.290 --> 16:58.120 -I'm gonna go get one of those job things. - -17:05.510 --> 17:07.050 -Hey, Monica! - -17:07.060 --> 17:08.370 -Hey Frannie, welcome back! - -17:08.370 --> 17:09.890 -How was Florida? - -17:09.920 --> 17:12.180 -You had sex, didn't you? - -17:13.340 --> 17:15.340 -- How do you do that? - -17:15.340 --> 17:17.590 -So? Who? - -17:17.590 --> 17:19.350 -You know Paul? - -17:19.790 --> 17:21.510 -Paul the Wine Guy? - -17:21.520 --> 17:23.510 -Oh yeah, I know Paul. - -17:25.660 --> 17:28.570 -You mean you know Paul like I know Paul? - -17:28.570 --> 17:31.000 -Are you kidding? I take credit for Paul. - -17:31.000 --> 17:35.600 -Y'know before me, there was no snap in his turtle for two years. - -17:38.280 --> 17:40.550 -Of course it was a line! - -17:41.130 --> 17:44.150 -Why?! Why? Why, why would anybody do something like that? - -17:44.150 --> 17:46.620 -I assume we're looking for an answer more sophisticated - -17:46.620 --> 17:49.440 -than 'to get you into bed'. - -17:50.380 --> 17:51.890 -Is it me? - -17:52.040 --> 17:54.830 -Is it like I have some sort of beacon - -17:54.830 --> 17:58.810 -that only dogs and men with severe emotional problems can hear? - -17:58.810 --> 18:00.940 -All right, c'mere, gimme your feet. - -18:08.220 --> 18:10.660 -I just thought he was nice, y'know? - -18:12.790 --> 18:15.650 -I can't believe you didn't know it was a line! - -18:20.040 --> 18:22.210 -- Guess what? - You got a job? - -18:22.210 --> 18:25.380 -Are you kidding? I'm trained for nothing! - -18:26.820 --> 18:29.120 -I was laughed out of twelve interviews today. - -18:29.120 --> 18:30.960 -And yet you're surprisingly upbeat. - -18:30.960 --> 18:34.370 -You would be too if you found John and David boots on sale, - -18:34.380 --> 18:36.280 -fifty percent off! - -18:36.610 --> 18:39.850 -Oh, how well you know me... - -18:39.850 --> 18:41.710 -They're my new 'I don't need a job, - -18:41.710 --> 18:44.720 -I don't need my parents, I've got great boots' boots! - -18:46.100 --> 18:47.650 -How'd you pay for them? - -18:47.650 --> 18:49.030 -Uh, credit card. - -18:49.030 --> 18:50.830 -And who pays for that? - -18:51.150 --> 18:53.410 -Um... my... father. - -18:55.860 --> 18:57.950 -C'mon, you can't live off your parents your whole life. - -18:57.950 --> 19:01.450 -I know that. That's why I was getting married. - -19:02.460 --> 19:05.580 -Give her a break, it's hard being on your own for the first time. - -19:05.580 --> 19:06.320 -Thank you. - -19:06.320 --> 19:08.850 -You're welcome. I remember when I first came to this city. - -19:08.850 --> 19:09.900 -I was fourteen. - -19:09.910 --> 19:12.910 -My mom had just killed herself and my step-dad was back in prison, - -19:12.910 --> 19:15.230 -and I got here, and I didn't know anybody. - -19:15.230 --> 19:17.630 -And I ended up living with this albino guy who was, like, - -19:17.630 --> 19:20.040 -cleaning windshields outside port authority, - -19:20.040 --> 19:21.900 -and then he killed himself, - -19:23.370 --> 19:25.000 -and then I found aromatherapy. - -19:25.010 --> 19:27.980 -So believe me, I know exactly how you feel. - -19:31.420 --> 19:34.240 -The word you're looking for is - -19:34.700 --> 19:36.520 -"Anyway"... - -19:40.170 --> 19:41.390 -All right, you ready? - -19:41.390 --> 19:42.310 -- I don't think so. - -19:42.310 --> 19:48.530 -C'mon, cut. Cut, cut, cut,... - -19:52.710 --> 19:54.660 -Welcome to the real world! - -19:54.660 --> 19:57.340 -It sucks. You're gonna love it! - -20:04.340 --> 20:07.870 -Well, that's it. You gonna crash on the couch? - -20:07.870 --> 20:10.340 -No. No, I gotta go home sometime. - -20:10.570 --> 20:12.320 -- You be okay? - Yeah. - -20:14.050 --> 20:16.760 -Hey Mon, look what I just found on the floor. - -20:18.120 --> 20:19.580 -What? - -20:19.940 --> 20:21.900 -That's Paul's watch. - -20:21.900 --> 20:26.510 -You just put it back where you found it. Oh boy - -20:26.510 --> 20:30.130 -- Alright. Goodnight, everybody. - Goodnight. - -20:41.950 --> 20:42.620 -- Sorry- - No no no, go- - -20:42.620 --> 20:43.810 -No, you have it, really, I don't want it - -20:43.810 --> 20:46.190 -- Split it? - Okay. - -20:50.490 --> 20:51.550 -You know you probably didn't know this, - -20:51.550 --> 20:53.850 -but back in high school, I had a, um, - -20:54.420 --> 20:56.640 -major crush on you. - -20:57.460 --> 20:58.980 -I knew. - -20:59.170 --> 21:00.880 -You did! Oh.... - -21:02.360 --> 21:03.340 -I always figured you just thought - -21:03.340 --> 21:06.060 -I was Monica's geeky older brother. - -21:06.060 --> 21:06.700 -I did. - -21:12.530 --> 21:14.090 -Oh. Listen, do you think - -21:14.600 --> 21:16.880 -and try not to let my intense vulnerability - -21:16.880 --> 21:19.070 -become any kind of a factor here - -21:20.020 --> 21:22.200 -but do you think it would be okay if I asked you out? - -21:22.200 --> 21:24.010 -Sometime? Maybe? - -21:25.250 --> 21:28.860 -Yeah, maybe... - -21:29.950 --> 21:38.530 -Okay... okay, maybe I will... - -21:42.890 --> 21:44.450 -- Goodnight. - Goodnight. - -21:58.470 --> 21:59.910 -See ya.... - -22:01.970 --> 22:05.180 -Waitwait, what's the deal? - -22:06.560 --> 22:08.840 -I just grabbed a spoon. - -22:17.490 --> 22:19.120 -I can't believe what I'm hearing here. - -22:19.120 --> 22:21.040 -I can't believe what I'm hearing here... - -22:21.040 --> 22:21.940 -What? I-I said you had a - -22:21.940 --> 22:23.850 -What I said you had... - -22:24.390 --> 22:25.910 -Would you stop? - -22:25.910 --> 22:28.420 -- Oh, was I doing it again? - Yes! - -22:29.710 --> 22:32.550 -Would anybody like more coffee? - -22:33.780 --> 22:36.220 -Did you make it, or are you just serving it? - -22:36.220 --> 22:38.680 -- I'm just serving it. - Yeah. Yeah, I'll have a cup of coffee. - -22:40.050 --> 22:41.680 -Kids, new dream... - -22:42.040 --> 22:43.310 -I'm in Las Vegas. - -22:43.310 --> 22:45.060 -I'm Liza Minelli- - -22:43.310 --> 22:45.060 - diff --git a/examples/macos/IJKMediaDemo/996747-5277368-31.m3u8 b/examples/macos/IJKMediaDemo/996747-5277368-31.m3u8 deleted file mode 100644 index 34481fa110..0000000000 --- a/examples/macos/IJKMediaDemo/996747-5277368-31.m3u8 +++ /dev/null @@ -1,28 +0,0 @@ -#EXTM3U -#EXT-X-TARGETDURATION:30 -#EXT-X-VERSION:3 -#EXTINF:8.08, -http://data.vod.itc.cn/m3u8?&start=0&end=7.88&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:25.08, -http://data.vod.itc.cn/m3u8?&start=7.88&end=32.96&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:26.36, -http://data.vod.itc.cn/m3u8?&start=32.96&end=59.32&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:25.599, -http://data.vod.itc.cn/m3u8?&start=59.32&end=84.919&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:25.32, -http://data.vod.itc.cn/m3u8?&start=84.919&end=110.239&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:25.361, -http://data.vod.itc.cn/m3u8?&start=110.239&end=135.6&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:28.36, -http://data.vod.itc.cn/m3u8?&start=135.6&end=163.96&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:27.64, -http://data.vod.itc.cn/m3u8?&start=163.96&end=191.6&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:28.64, -http://data.vod.itc.cn/m3u8?&start=191.6&end=220.24&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:32.56, -http://data.vod.itc.cn/m3u8?&start=220.24&end=252.8&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:30.24, -http://data.vod.itc.cn/m3u8?&start=252.8&end=283.04&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXTINF:16.762, -http://data.vod.itc.cn/m3u8?&start=283.04&end=300.002&k=hWODtfkIlB6XfOcsWO1HbFeGeYdUuK87zh6RPToBbEXUqhN6PJXUyYbS0pbclD6SotNcgmeUqFo7NF2i0MsUwm1UoTbcWhk&a=hWqbzHJUhWqFjfaptUJlzSwdopwdopNGoprGop1WjWlvzSkAqmXmqL1WXfXIgmeUqFo2gTPcgmeUqFoAuT8cWr&sig=b7WDmR2hX6oyvSxXK3feqLZZc6W8zam7&pt=1&prod=ifox&pg=1&ca=2&cv=3.0&uid=F45C89AE5BC3&qd=8001 -#EXT-X-ENDLIST \ No newline at end of file diff --git a/examples/macos/IJKMediaDemo/AppDelegate.m b/examples/macos/IJKMediaDemo/AppDelegate.m index 69d98ea858..7148a69af1 100644 --- a/examples/macos/IJKMediaDemo/AppDelegate.m +++ b/examples/macos/IJKMediaDemo/AppDelegate.m @@ -17,6 +17,7 @@ #import "MRActionKit.h" #import "MRTextInfoViewController.h" #import +#import "MRCocoaBindingUserDefault.h" @interface AppDelegate () @@ -46,7 +47,7 @@ - (void)prepareActionProcessor [NSApp activateIgnoringOtherApps:YES]; } forPath:@"/play"]; - [MRUtil initUserDefaults]; + [MRCocoaBindingUserDefault initUserDefaults]; [MRActionManager registerProcessor:processor]; } @@ -66,8 +67,68 @@ - (void)applicationWillFinishLaunching:(NSNotification *)notification [self prepareActionProcessor]; } +#pragma mark 日志级别 + +- (int)levelWithString:(NSString *)str +{ + str = [str lowercaseString]; + if ([str isEqualToString:@"default"]) { + return k_IJK_LOG_DEFAULT; + } else if ([str isEqualToString:@"verbose"]) { + return k_IJK_LOG_VERBOSE; + } else if ([str isEqualToString:@"debug"]) { + return k_IJK_LOG_DEBUG; + } else if ([str isEqualToString:@"info"]) { + return k_IJK_LOG_INFO; + } else if ([str isEqualToString:@"warn"]) { + return k_IJK_LOG_WARN; + } else if ([str isEqualToString:@"error"]) { + return k_IJK_LOG_ERROR; + } else if ([str isEqualToString:@"fatal"]) { + return k_IJK_LOG_FATAL; + } else if ([str isEqualToString:@"silent"]) { + return k_IJK_LOG_SILENT; + } else { + return k_IJK_LOG_UNKNOWN; + } +} + +- (void)reSetLoglevel +{ + NSString *loglevel = [MRCocoaBindingUserDefault log_level]; + NSLog(@"IJK LogLevel set:%@",loglevel); + int level = [self levelWithString:loglevel]; + [IJKFFMoviePlayerController setLogLevel:level]; +} + - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { +#if DEBUG + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"]; +#endif + + __weakSelf__ + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull value,BOOL *removed) { + __strongSelf__ + [self reSetLoglevel]; + } forKey:@"log_level"]; + + static NSDateFormatter *df; + if (!df) { + df = [[NSDateFormatter alloc]init]; +#if DEBUG + df.dateFormat = @"HH:mm:ss SSS"; +#else + df.dateFormat = @"yyyy-MM-dd HH:mm:ss S"; +#endif + } + + [IJKFFMoviePlayerController setLogHandler:^(IJKLogLevel level, NSString *tag, NSString *msg) { + NSString *dateStr = [df stringFromDate:[NSDate date]]; + NSLog(@"[%@] [%@] %@", dateStr, tag, msg); + }]; + + [self reSetLoglevel]; // Insert code here to initialize your application NSWindowStyleMask mask = NSWindowStyleMaskBorderless | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView; @@ -76,7 +137,9 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification window.contentViewController = [[MRRootViewController alloc] init]; // window.contentViewController = [[MRAutoTestViewController alloc] init]; // window.contentViewController = [[MRStatisticalViewController alloc] init]; - + if (window.contentViewController.title) { + window.title = window.contentViewController.title; + } window.movableByWindowBackground = YES; self.windowCtrl = [[WindowController alloc] init]; @@ -140,8 +203,7 @@ - (void)openDocument:(id)sender - (IBAction)showPreferencesPanel:(id)sender { - NSStoryboard *sb = [NSStoryboard storyboardWithName:@"Setting" bundle:nil]; - [self.windowCtrl.window.contentViewController presentViewControllerAsModalWindow:[sb instantiateInitialController]]; + } - (IBAction)showSupportedDecoder:(id)sender diff --git a/examples/macos/IJKMediaDemo/DragView/MRDragView.m b/examples/macos/IJKMediaDemo/DragView/MRDragView.m index bb7085fa7f..085c72e57b 100644 --- a/examples/macos/IJKMediaDemo/DragView/MRDragView.m +++ b/examples/macos/IJKMediaDemo/DragView/MRDragView.m @@ -74,7 +74,7 @@ - (NSArray *)draggedFileList:(id _Nonnull)sender - (NSDragOperation)draggingEntered:(id )sender { NSArray * list = [self draggedFileList:sender]; - if (self.delegate) { + if (self.delegate && [self.delegate respondsToSelector:@selector(acceptDragOperation:)]) { return [self.delegate acceptDragOperation:list]; } return NSDragOperationNone; @@ -84,7 +84,7 @@ - (NSDragOperation)draggingEntered:(id )sender - (BOOL)prepareForDragOperation:(id)sender { NSArray * list = [self draggedFileList:sender]; - if (list.count && self.delegate) { + if (list.count && self.delegate && [self.delegate respondsToSelector:@selector(handleDragFileList:)]) { [self.delegate handleDragFileList:list]; } return YES; diff --git a/examples/macos/IJKMediaDemo/MRAutoTestViewController.m b/examples/macos/IJKMediaDemo/MRAutoTestViewController.m index 7644140c93..4bfc534d45 100644 --- a/examples/macos/IJKMediaDemo/MRAutoTestViewController.m +++ b/examples/macos/IJKMediaDemo/MRAutoTestViewController.m @@ -20,6 +20,7 @@ #import "MRBaseView.h" #import "MultiRenderSample.h" #import "NSString+Ex.h" +#import "MRCocoaBindingUserDefault.h" static NSString* lastPlayedKey = @"__lastPlayedKey"; static BOOL hdrAnimationShown = 0; @@ -95,7 +96,7 @@ - (void)viewDidLoad { //for debug //[self.view setWantsLayer:YES]; //self.view.layer.backgroundColor = [[NSColor redColor] CGColor]; - + self.title = @"AutoTest"; [IJKFFMoviePlayerController setLogHandler:^(IJKLogLevel level, NSString *tag, NSString *msg) { NSLog(@"[%@] [%d] %@",tag,level,msg); // printf("[%s] %s\n",[tag UTF8String],[msg UTF8String]); @@ -617,7 +618,7 @@ - (void)perpareIJKPlayer:(NSURL *)url hwaccel:(BOOL)hwaccel // [options setPlayerOptionIntValue:1 forKey:@"an"]; // [options setPlayerOptionIntValue:1 forKey:@"nodisp"]; - [options setPlayerOptionIntValue:[MRUtil boolForKey:@"values.copy_hw_frame"] forKey:@"copy_hw_frame"]; + [options setPlayerOptionIntValue:[MRCocoaBindingUserDefault copy_hw_frame] forKey:@"copy_hw_frame"]; if ([url isFileURL]) { //图片不使用 cvpixelbufferpool NSString *ext = [[[url path] pathExtension] lowercaseString]; @@ -689,7 +690,7 @@ - (void)perpareIJKPlayer:(NSURL *)url hwaccel:(BOOL)hwaccel rect.origin = CGPointZero; playerView.frame = rect; playerView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; - [self.view addSubview:playerView positioned:NSWindowBelow relativeTo:nil]; + [self.view addSubview:playerView positioned:NSWindowBelow relativeTo:self.playerCtrlPanel]; playerView.showHdrAnimation = !hdrAnimationShown; //playerView.preventDisplay = YES; @@ -965,12 +966,6 @@ - (void)playURL:(NSURL *)url [self onReset:nil]; self.playCtrlBtn.state = NSControlStateValueOn; - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; - p.bottomMargin = self.subtitleMargin; - NSNumber *number = [[NSUserDefaultsController sharedUserDefaultsController] valueForKeyPath:@"values.subtitleFontRatio"]; - p.ratio = [number floatValue]; - self.player.view.subtitlePreference = p; - [self.player prepareToPlay]; if ([self.subtitles count] > 0) { @@ -1048,15 +1043,47 @@ - (NSURL *)existTaskForUrl:(NSURL *)url - (void)appendToPlayList:(NSArray *)bookmarkArr reset:(BOOL)reset { + NSMutableArray *videos = [NSMutableArray array]; + NSMutableArray *subtitles = [NSMutableArray array]; + for (NSDictionary *dic in bookmarkArr) { NSURL *url = dic[@"url"]; + + if ([self existTaskForUrl:url]) { + continue; + } if ([[[url pathExtension] lowercaseString] isEqualToString:@"xlist"]) { if (reset) { [self.playList removeAllObjects]; } [self loadNASPlayList:url]; + continue; + } + if ([dic[@"type"] intValue] == 0) { + [videos addObject:url]; + } else if ([dic[@"type"] intValue] == 1) { + [subtitles addObject:url]; + } else { + NSAssert(NO, @"没有处理的文件:%@",url); } } + + if ([videos count] > 0) { + if (reset) { + [self.playList removeAllObjects]; + } + [self.playList addObjectsFromArray:videos]; + [self playFirstIfNeed]; + } + + if ([subtitles count] > 0) { + [self.subtitles addObjectsFromArray:subtitles]; + + NSURL *firstUrl = [subtitles firstObject]; + [subtitles removeObjectAtIndex:0]; + [self.player loadThenActiveSubtitle:firstUrl]; + [self.player loadSubtitlesOnly:subtitles]; + } } #pragma mark - 拖拽 @@ -1103,6 +1130,8 @@ - (NSDragOperation)acceptDragOperation:(NSArray *)list NSString *pathExtension = [[url pathExtension] lowercaseString]; if ([@"xlist" isEqualToString:pathExtension]) { return NSDragOperationCopy; + } else if ([[MRUtil acceptMediaType] containsObject:pathExtension]) { + return NSDragOperationCopy; } } } @@ -1156,7 +1185,7 @@ - (IBAction)onMoreFunc:(id)sender - (BOOL)preferHW { - return [MRUtil boolForKey:@"values.hw"]; + return [MRCocoaBindingUserDefault use_hw]; } - (void)retry @@ -1321,22 +1350,16 @@ - (IBAction)onChangeSubtitleColor:(NSPopUpButton *)sender { NSMenuItem *item = [sender selectedItem]; int bgrValue = (int)item.tag; - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; + IJKSDLSubtitlePreference p = self.player.subtitlePreference; p.color = bgrValue; - self.player.view.subtitlePreference = p; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } + self.player.subtitlePreference = p; } - (IBAction)onChangeSubtitleSize:(NSStepper *)sender { - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; - p.ratio = sender.floatValue; - self.player.view.subtitlePreference = p; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } + IJKSDLSubtitlePreference p = self.player.subtitlePreference; + p.scale = sender.floatValue / 50; + self.player.subtitlePreference = p; } - (IBAction)onSelectSubtitle:(NSPopUpButton*)sender @@ -1360,12 +1383,9 @@ - (IBAction)onChangeSubtitleDelay:(NSStepper *)sender - (IBAction)onChangeSubtitleBottomMargin:(NSSlider *)sender { - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; + IJKSDLSubtitlePreference p = self.player.subtitlePreference; p.bottomMargin = sender.floatValue; - self.player.view.subtitlePreference = p; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } + self.player.subtitlePreference = p; } #pragma mark 画面设置 diff --git a/examples/macos/IJKMediaDemo/MRCocoaBindingUserDefault.h b/examples/macos/IJKMediaDemo/MRCocoaBindingUserDefault.h new file mode 100644 index 0000000000..a1b8fbace5 --- /dev/null +++ b/examples/macos/IJKMediaDemo/MRCocoaBindingUserDefault.h @@ -0,0 +1,72 @@ +// +// MRCocoaBindingUserDefault.h +// IJKMediaMacDemo +// +// Created by Reach Matt on 2024/1/25. +// Copyright © 2024 IJK Mac. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +@class NSColor; +@interface MRCocoaBindingUserDefault : NSObject + ++ (void)initUserDefaults; ++ (void)resetAll; + ++ (void)setValue:(id)value forKey:(NSString *)key; ++ (void)resetValueForKey:(NSString *)key; ++ (id)anyForKey:(NSString *)key; ++ (BOOL)boolForKey:(NSString *)key; ++ (NSString *)stringForKey:(NSString *)key; ++ (MRCocoaBindingUserDefault *)sharedDefault; +//block BOOL means after invoke wheather stop ovserve and remove the observer +- (void)onChange:(void(^)(id,BOOL*))observer forKey:(NSString *)keyPath; +- (void)onChange:(void(^)(id,BOOL*))observer forKey:(NSString *)key init:(BOOL)init; +@end + +@interface MRCocoaBindingUserDefault (util) + ++ (float)volume; ++ (void)setVolume:(float)aVolume; + ++ (NSString *)log_level; + ++ (float)color_adjust_brightness; ++ (float)color_adjust_saturation; ++ (float)color_adjust_contrast; + ++ (int)picture_fill_mode; ++ (int)picture_wh_ratio; ++ (int)picture_ratate_mode; ++ (int)picture_flip_mode; + ++ (BOOL)copy_hw_frame; ++ (BOOL)use_hw; + ++ (BOOL)accurate_seek; ++ (int)seek_step; ++ (int)lock_screen_ratio; + ++ (int)open_gzip; ++ (int)use_dns_cache; ++ (int)dns_cache_period; + ++ (NSString *)subtitle_font_name; ++ (void)setSubtitle_font_name:(NSString *)font_name; ++ (float)subtitle_scale; ++ (void)setSubtitle_font_size:(float)font_size; ++ (int)subtitle_bottom_margin; ++ (float)subtitle_stroke_size; ++ (NSColor *)subtitle_text_color; ++ (NSColor *)subtitle_bg_color; ++ (NSColor *)subtitle_stroke_color; + ++ (NSString *)overlay_format; ++ (BOOL)use_opengl; ++ (int)snapshot_type; + +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/macos/IJKMediaDemo/MRCocoaBindingUserDefault.m b/examples/macos/IJKMediaDemo/MRCocoaBindingUserDefault.m new file mode 100644 index 0000000000..569a684fce --- /dev/null +++ b/examples/macos/IJKMediaDemo/MRCocoaBindingUserDefault.m @@ -0,0 +1,389 @@ +// +// MRCocoaBindingUserDefault.m +// IJKMediaMacDemo +// +// Created by Reach Matt on 2024/1/25. +// Copyright © 2024 IJK Mac. All rights reserved. +// +//https://itecnote.com/tecnote/ios-nsuserdefaultsdidchangenotification-whats-the-name-of-the-key-that-changed/ + +//https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaBindings/Concepts/NSUserDefaultsController.html + +#import "MRCocoaBindingUserDefault.h" +#import +#import + +@interface MRCocoaBindingUserDefault() + +@property (nonatomic, strong) NSMutableDictionary *observers; + +@end + +@implementation MRCocoaBindingUserDefault + ++ (MRCocoaBindingUserDefault *)sharedDefault +{ + static MRCocoaBindingUserDefault *obj = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + obj = [[MRCocoaBindingUserDefault alloc] init]; + }); + return obj; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + self.observers = [NSMutableDictionary dictionary]; + } + return self; +} + ++ (NSDictionary *)initValues +{ + NSColor *text_color = [NSColor whiteColor]; + NSData *text_color_data = [NSKeyedArchiver archivedDataWithRootObject:text_color]; + + NSColor *subtitle_bg_color = [NSColor clearColor]; + NSData *subtitle_bg_color_data = [NSKeyedArchiver archivedDataWithRootObject:subtitle_bg_color]; + + NSColor *subtitle_stroke_color = [NSColor blackColor]; + NSData *subtitle_stroke_color_data = [NSKeyedArchiver archivedDataWithRootObject:subtitle_stroke_color]; + + NSDictionary *initValues = @{ + @"volume" : @(0.4), + + @"log_level":@"info", + @"color_adjust_brightness" : @(1.0), + @"color_adjust_saturation" : @(1.0), + @"color_adjust_contrast" : @(1.0), + @"use_opengl" : @(0), + @"picture_fill_mode" : @(0), + @"picture_wh_ratio" : @(0), + @"picture_ratate_mode" : @(0), + @"picture_flip_mode" : @(0), + + @"use_hw" : @(1), + @"copy_hw_frame" : @(0), + @"de_interlace" : @(0), + @"open_hdr" : @(1), + @"overlay_format" : @"fcc-_es2", + + @"subtitle_font_name" : @"STSongti-SC-Regular", + @"subtitle_scale" : @(1.0), + @"subtitle_bottom_margin":@(20), + @"subtitle_stroke_size" : @(5), + @"subtitle_text_color" : text_color_data, + @"subtitle_bg_color" : subtitle_bg_color_data, + @"subtitle_stroke_color" : subtitle_stroke_color_data, + + @"snapshot_type" : @(3), + @"accurate_seek" : @(1), + @"seek_step" : @(15), + @"lock_screen_ratio" : @(1), + + @"open_gzip" : @(1), + @"use_dns_cache" : @(1), + @"dns_cache_period" : @(600), + }; + return initValues; +} + ++ (void)initUserDefaults +{ + NSDictionary * initValues = [self initValues]; + [[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:initValues]; + [[[NSUserDefaultsController sharedUserDefaultsController] defaults] registerDefaults:initValues]; +} + ++ (void)resetAll +{ + NSDictionary * initValues = [self initValues]; + [[[NSUserDefaultsController sharedUserDefaultsController] defaults] setPersistentDomain:initValues forName:[[NSBundle mainBundle] bundleIdentifier]]; + + //清理掉现有的值 +// [[[NSUserDefaultsController sharedUserDefaultsController] defaults] removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]]; +} + ++ (NSString *)resolveKey:(NSString *)key +{ + if (!key) { + return nil; + } + if (![key hasPrefix:@"values."]) { + key = [@"values." stringByAppendingString:key]; + } + return key; +} + ++ (id)anyForKey:(NSString *)key +{ + key = [self resolveKey:key]; + return [[NSUserDefaultsController sharedUserDefaultsController] valueForKeyPath:key]; +} + ++ (void)setValue:(id)value forKey:(NSString *)key +{ + key = [self resolveKey:key]; + [[NSUserDefaultsController sharedUserDefaultsController] setValue:value forKeyPath:key]; +} + ++ (void)resetValueForKey:(NSString *)key +{ + key = [self resolveKey:key]; + id initValue = [[[NSUserDefaultsController sharedUserDefaultsController] initialValues] objectForKey:key]; + [self setValue:initValue forKey:key]; +} + ++ (BOOL)boolForKey:(NSString *)key +{ + return [[self anyForKey:key] boolValue]; +} + ++ (float)floatForKey:(NSString *)key +{ + return [[self anyForKey:key] floatValue]; +} + ++ (NSString *)stringForKey:(NSString *)key +{ + return [[self anyForKey:key] description]; +} + ++ (int)intForKey:(NSString *)key +{ + return [[self anyForKey:key] intValue]; +} + +- (void)onChange:(void(^)(id,BOOL*))observer forKey:(NSString *)key +{ + [self onChange:observer forKey:key init:NO]; +} + +- (void)onChange:(void(^)(id,BOOL*))observer forKey:(NSString *)key init:(BOOL)init +{ + if (!observer) { + return; + } + BOOL remove = NO; + if (init) { + id value = [[self class] anyForKey:key]; + observer(value, &remove); + } + + if (!remove) { + NSMutableArray *array = [self.observers objectForKey:key]; + if (!array) { + array = [NSMutableArray array]; + [self.observers setObject:array forKey:key]; + NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults]; + [defaults addObserver:self + forKeyPath:key + options:NSKeyValueObservingOptionNew + context:NULL]; + } + [array addObject:[observer copy]]; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + NSArray *array = [self.observers objectForKey:keyPath]; + id value = change[NSKeyValueChangeNewKey]; + NSMutableArray *removeArr = nil; + for (int i = 0; i< array.count; i++) { + void(^block)(id,BOOL*) = array[i]; + BOOL remove = NO; + if ([value isKindOfClass:[NSData class]]) { + value = [NSKeyedUnarchiver unarchiveObjectWithData:value]; + } + block(value, &remove); + if (remove) { + if (removeArr == nil) { + removeArr = [NSMutableArray array]; + } + [removeArr addObject:@(i)]; + } + } + if ([removeArr count] > 0) { + NSMutableArray *result = [NSMutableArray arrayWithArray:array]; + NSEnumerator *enumerator = [removeArr reverseObjectEnumerator]; + id obj = nil; + while (obj = [enumerator nextObject]) { + [result removeObjectAtIndex:[obj intValue]]; + } + [self.observers setObject:result forKey:keyPath]; + } +} + +@end + +@implementation MRCocoaBindingUserDefault (util) + ++ (NSString *)log_level +{ + return [self stringForKey:@"log_level"]; +} + ++ (float)color_adjust_brightness +{ + return [self floatForKey:@"color_adjust_brightness"]; +} + ++ (float)color_adjust_saturation +{ + return [self floatForKey:@"color_adjust_saturation"]; +} + ++ (float)color_adjust_contrast +{ + return [self floatForKey:@"color_adjust_contrast"]; +} + ++ (int)picture_fill_mode +{ + return [self intForKey:@"picture_fill_mode"]; +} + ++ (int)picture_wh_ratio +{ + return [self intForKey:@"picture_wh_ratio"]; +} + ++ (int)picture_ratate_mode +{ + return [self intForKey:@"picture_ratate_mode"]; +} + ++ (int)picture_flip_mode +{ + return [self intForKey:@"picture_flip_mode"]; +} + ++ (BOOL)copy_hw_frame +{ + return [self boolForKey:@"copy_hw_frame"]; +} + ++ (BOOL)use_hw +{ + return [self boolForKey:@"use_hw"]; +} + ++ (NSString *)subtitle_font_name +{ + return [self stringForKey:@"subtitle_font_name"]; +} + ++ (void)setSubtitle_font_name:(NSString *)font_name +{ + return [self setValue:font_name forKey:@"subtitle_font_name"]; +} + ++ (float)subtitle_scale +{ + return [self floatForKey:@"subtitle_scale"]; +} + ++ (void)setSubtitle_font_size:(float)font_size +{ + return [self setValue:@(font_size) forKey:@"subtitle_font_size"]; +} + ++ (int)subtitle_bottom_margin +{ + return [self intForKey:@"subtitle_bottom_margin"]; +} + ++ (float)subtitle_stroke_size +{ + return [self floatForKey:@"subtitle_stroke_size"]; +} + ++ (NSColor *)subtitle_text_color +{ + NSData *data = [self anyForKey:@"subtitle_text_color"]; + if (data) { + return [NSKeyedUnarchiver unarchiveObjectWithData:data]; + } + return nil; +} + ++ (NSColor *)subtitle_bg_color +{ + NSData *data = [self anyForKey:@"subtitle_bg_color"]; + if (data) { + return [NSKeyedUnarchiver unarchiveObjectWithData:data]; + } + return nil; +} + ++ (NSColor *)subtitle_stroke_color +{ + NSData *data = [self anyForKey:@"subtitle_stroke_color"]; + if (data) { + return [NSKeyedUnarchiver unarchiveObjectWithData:data]; + } + return nil; +} + ++ (float)volume +{ + return [self floatForKey:@"volume"]; +} + ++ (void)setVolume:(float)aVolume +{ + [self setValue:@(aVolume) forKey:@"volume"]; +} + ++ (NSString *)overlay_format +{ + return [self stringForKey:@"overlay_format"]; +} + ++ (BOOL)use_opengl +{ + return [self boolForKey:@"use_opengl"]; +} + ++ (int)snapshot_type +{ + return [self intForKey:@"snapshot_type"]; +} + ++ (BOOL)accurate_seek +{ + return [self boolForKey:@"accurate_seek"]; +} + ++ (int)seek_step +{ + return [self intForKey:@"seek_step"]; +} + ++ (int)lock_screen_ratio +{ + return [self intForKey:@"lock_screen_ratio"]; +} + ++ (int)open_gzip +{ + return [self intForKey:@"open_gzip"]; +} + ++ (int)use_dns_cache +{ + return [self intForKey:@"use_dns_cache"]; +} + ++ (int)dns_cache_period +{ + return [self intForKey:@"dns_cache_period"]; +} + +@end diff --git a/examples/macos/IJKMediaDemo/MRPlayerSettingsViewController.h b/examples/macos/IJKMediaDemo/MRPlayerSettingsViewController.h new file mode 100644 index 0000000000..617e349b48 --- /dev/null +++ b/examples/macos/IJKMediaDemo/MRPlayerSettingsViewController.h @@ -0,0 +1,28 @@ +// +// MRPlayerSettingsViewController.h +// IJKMediaMacDemo +// +// Created by Reach Matt on 2024/1/24. +// Copyright © 2024 IJK Mac. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void(^MRPlayerSettingsExchangeStreamBlock)(int); +typedef void(^MRPlayerSettingsCloseStreamBlock)(NSString *); + +@interface MRPlayerSettingsViewController : NSViewController + ++ (float)viewWidth; + +- (void)exchangeToNextSubtitle; +- (void)updateTracks:(NSDictionary *)dic; +- (void)onCloseCurrentStream:(MRPlayerSettingsCloseStreamBlock)block; +- (void)onExchangeSelectedStream:(MRPlayerSettingsExchangeStreamBlock)block; +- (void)onCaptureShot:(dispatch_block_t)block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/macos/IJKMediaDemo/MRPlayerSettingsViewController.m b/examples/macos/IJKMediaDemo/MRPlayerSettingsViewController.m new file mode 100644 index 0000000000..05cd067657 --- /dev/null +++ b/examples/macos/IJKMediaDemo/MRPlayerSettingsViewController.m @@ -0,0 +1,245 @@ +// +// MRPlayerSettingsViewController.m +// IJKMediaMacDemo +// +// Created by Reach Matt on 2024/1/24. +// Copyright © 2024 IJK Mac. All rights reserved. +// + +#import "MRPlayerSettingsViewController.h" +#import +#import "MRCocoaBindingUserDefault.h" + +@interface MRPlayerSettingsViewController () + +@property (weak) IBOutlet NSScrollView *scrollView; + +@property (nonatomic, weak) IBOutlet NSPopUpButton *subtitlePopUpBtn; +@property (nonatomic, weak) IBOutlet NSPopUpButton *audioPopUpBtn; +@property (nonatomic, weak) IBOutlet NSPopUpButton *videoPopUpBtn; + +@property (nonatomic, assign) BOOL use_openGL; +@property (nonatomic, copy) NSString *fcc; +@property (nonatomic, assign) int snapshot; +@property (nonatomic, assign) BOOL accurateSeek; +//for cocoa binding end + +@property (nonatomic, copy) MRPlayerSettingsCloseStreamBlock closeCurrentStream; +@property (nonatomic, copy) MRPlayerSettingsExchangeStreamBlock exchangeSelectedStream; +@property (nonatomic, copy) dispatch_block_t captureShot; + +@property (nonatomic, strong) NSFont *font; + +@end + +@implementation MRPlayerSettingsViewController + ++ (float)viewWidth +{ + return 300; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do view setup here. +} + +- (void)viewDidAppear +{ + [super viewDidAppear]; + NSPoint newOrigin = NSMakePoint(0, NSMaxY(self.scrollView.documentView.frame) - self.scrollView.bounds.size.height); + [self.scrollView.contentView scrollToPoint:newOrigin]; +} + +- (void)onCloseCurrentStream:(MRPlayerSettingsCloseStreamBlock)block +{ + self.closeCurrentStream = block; +} + +- (void)onExchangeSelectedStream:(MRPlayerSettingsExchangeStreamBlock)block +{ + self.exchangeSelectedStream = block; +} + +- (void)exchangeToNextSubtitle +{ + NSInteger idx = [self.subtitlePopUpBtn indexOfSelectedItem]; + idx ++; + if (idx >= [self.subtitlePopUpBtn numberOfItems]) { + idx = 0; + } + NSMenuItem *item = [self.subtitlePopUpBtn itemAtIndex:idx]; + if (item) { + [self.subtitlePopUpBtn selectItem:item]; + [self.subtitlePopUpBtn.target performSelector:self.subtitlePopUpBtn.action withObject:self.subtitlePopUpBtn]; + } +} + +- (void)updateTracks:(NSDictionary *)mediaMeta +{ + int audioIdx = [mediaMeta[k_IJKM_VAL_TYPE__AUDIO] intValue]; + NSLog(@"当前音频:%d",audioIdx); + int videoIdx = [mediaMeta[k_IJKM_VAL_TYPE__VIDEO] intValue]; + NSLog(@"当前视频:%d",videoIdx); + int subtitleIdx = [mediaMeta[k_IJKM_VAL_TYPE__SUBTITLE] intValue]; + NSLog(@"当前字幕:%d",subtitleIdx); + + [self.subtitlePopUpBtn removeAllItems]; + NSString *currentTitle = @"选择字幕"; + [self.subtitlePopUpBtn addItemWithTitle:currentTitle]; + + [self.audioPopUpBtn removeAllItems]; + NSString *currentAudio = @"选择音轨"; + [self.audioPopUpBtn addItemWithTitle:currentAudio]; + + [self.videoPopUpBtn removeAllItems]; + NSString *currentVideo = @"选择视轨"; + [self.videoPopUpBtn addItemWithTitle:currentVideo]; + + for (NSDictionary *stream in mediaMeta[kk_IJKM_KEY_STREAMS]) { + NSString *type = stream[k_IJKM_KEY_TYPE]; + int streamIdx = [stream[k_IJKM_KEY_STREAM_IDX] intValue]; + if ([type isEqualToString:k_IJKM_VAL_TYPE__SUBTITLE]) { + NSLog(@"subtile meta:%@",stream); + NSString *url = stream[k_IJKM_KEY_EX_SUBTITLE_URL]; + NSString *title = nil; + if (url) { + title = [[url lastPathComponent] stringByRemovingPercentEncoding]; + } else { + title = stream[k_IJKM_KEY_TITLE]; + if (title.length == 0) { + title = stream[k_IJKM_KEY_LANGUAGE]; + } + if (title.length == 0) { + title = @"未知"; + } + } + title = [NSString stringWithFormat:@"%@-%d",title,streamIdx]; + if ([mediaMeta[k_IJKM_VAL_TYPE__SUBTITLE] intValue] == streamIdx) { + currentTitle = title; + } + [self.subtitlePopUpBtn addItemWithTitle:title]; + } else if ([type isEqualToString:k_IJKM_VAL_TYPE__AUDIO]) { + NSLog(@"audio meta:%@",stream); + NSString *title = stream[k_IJKM_KEY_TITLE]; + if (title.length == 0) { + title = stream[k_IJKM_KEY_LANGUAGE]; + } + if (title.length == 0) { + title = @"未知"; + } + title = [NSString stringWithFormat:@"%@-%d",title,streamIdx]; + if ([mediaMeta[k_IJKM_VAL_TYPE__AUDIO] intValue] == streamIdx) { + currentAudio = title; + } + [self.audioPopUpBtn addItemWithTitle:title]; + } else if ([type isEqualToString:k_IJKM_VAL_TYPE__VIDEO]) { + NSLog(@"video meta:%@",stream); + NSString *title = stream[k_IJKM_KEY_TITLE]; + if (title.length == 0) { + title = stream[k_IJKM_KEY_LANGUAGE]; + } + if (title.length == 0) { + title = @"未知"; + } + title = [NSString stringWithFormat:@"%@-%d",title,streamIdx]; + if ([mediaMeta[k_IJKM_VAL_TYPE__VIDEO] intValue] == streamIdx) { + currentVideo = title; + } + [self.videoPopUpBtn addItemWithTitle:title]; + } + } + [self.subtitlePopUpBtn selectItemWithTitle:currentTitle]; + [self.audioPopUpBtn selectItemWithTitle:currentAudio]; + [self.videoPopUpBtn selectItemWithTitle:currentVideo]; +} + +#pragma mark 音轨设置 + +- (IBAction)onSelectTrack:(NSPopUpButton*)sender +{ + if (sender.indexOfSelectedItem == 0) { + if (self.closeCurrentStream) { + if (sender.tag == 1) { + self.closeCurrentStream(k_IJKM_VAL_TYPE__AUDIO); + } else if (sender.tag == 2) { + self.closeCurrentStream(k_IJKM_VAL_TYPE__VIDEO); + } else if (sender.tag == 3) { + self.closeCurrentStream(k_IJKM_VAL_TYPE__SUBTITLE); + } + } + } else { + NSString *title = sender.selectedItem.title; + NSArray *items = [title componentsSeparatedByString:@"-"]; + int idx = [[items lastObject] intValue]; + if (sender.tag == 1) { + NSLog(@"SelectAudioTrack:%d",idx); + } else if (sender.tag == 2) { + NSLog(@"SelectVideoTrack:%d",idx); + } else if (sender.tag == 3) { + NSLog(@"SelectSubtitleTrack:%d",idx); + } + + if (self.exchangeSelectedStream) { + self.exchangeSelectedStream(idx); + } + } +} + +- (IBAction)onResetColorAdjust:(NSButton *)sender +{ + int tag = (int)sender.tag; + NSString *key = nil; + if (tag == 1) { + key = @"color_adjust_brightness"; + } else if (tag == 2){ + key = @"color_adjust_saturation"; + } else if (tag == 3){ + key = @"color_adjust_contrast"; + } + if (key) { + [MRCocoaBindingUserDefault resetValueForKey:key]; + } +} + +- (IBAction)onSelectFont:(NSButton *)sender +{ + NSFontManager *fontManager = [NSFontManager sharedFontManager]; + [fontManager setTarget:self]; + NSFontPanel *panel = [fontManager fontPanel:YES]; + int fontSize = [MRCocoaBindingUserDefault subtitle_scale] * 50; + NSFont *font = [NSFont fontWithName:[MRCocoaBindingUserDefault subtitle_font_name] size:fontSize]; + if (!font) { + font = [NSFont systemFontOfSize:fontSize]; + } + self.font = font; + [panel setPanelFont:self.font isMultiple:NO]; + [[self.view window] makeFirstResponder:panel]; + [panel orderFront:self]; +} + +- (void)changeFont:(NSFontManager *)sender +{ + self.font = [[NSFontPanel sharedFontPanel] panelConvertFont:self.font]; + [MRCocoaBindingUserDefault setSubtitle_font_name:self.font.fontName]; + [MRCocoaBindingUserDefault setSubtitle_font_size:self.font.pointSize]; +} + +- (void)onCaptureShot:(dispatch_block_t)block +{ + self.captureShot = block; +} + +- (IBAction)onSnapshot:(NSButton *)sender +{ + if (self.captureShot) { + self.captureShot(); + } +} + +- (IBAction)onRestAllSettings:(NSButton *)sender +{ + [MRCocoaBindingUserDefault resetAll]; +} + +@end diff --git a/examples/macos/IJKMediaDemo/MRPlayerSettingsViewController.xib b/examples/macos/IJKMediaDemo/MRPlayerSettingsViewController.xib new file mode 100644 index 0000000000..19235a1908 --- /dev/null +++ b/examples/macos/IJKMediaDemo/MRPlayerSettingsViewController.xib @@ -0,0 +1,1003 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSKeyedUnarchiveFromData + + + + + + + + + + + + + + + + + + + + + NSKeyedUnarchiveFromData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NSKeyedUnarchiveFromData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/macos/IJKMediaDemo/MRPlaylistRowView.h b/examples/macos/IJKMediaDemo/MRPlaylistRowView.h new file mode 100644 index 0000000000..ef69164191 --- /dev/null +++ b/examples/macos/IJKMediaDemo/MRPlaylistRowView.h @@ -0,0 +1,34 @@ +// +// MRPlaylistRowView.h +// IJKMediaMacDemo +// +// Created by Reach Matt on 2024/1/24. +// Copyright © 2024 IJK Mac. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef enum : NSUInteger { + KSeparactorStyleFull, + KSeparactorStyleHeadPadding, + KSeparactorStyleNone, +} KSeparactorStyle; + +@interface MRPlaylistRowData : NSObject + +@property(nonatomic) NSString *key; +@property(nonatomic) NSString *value; + +@end + +@interface MRPlaylistRowView : NSTableRowView + +@property KSeparactorStyle sepStyle; + +- (void)updateData:(MRPlaylistRowData *)data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/macos/IJKMediaDemo/MRPlaylistRowView.m b/examples/macos/IJKMediaDemo/MRPlaylistRowView.m new file mode 100644 index 0000000000..7ce135cb35 --- /dev/null +++ b/examples/macos/IJKMediaDemo/MRPlaylistRowView.m @@ -0,0 +1,143 @@ +// +// MRPlaylistRowView.m +// IJKMediaMacDemo +// +// Created by Reach Matt on 2024/1/24. +// Copyright © 2024 IJK Mac. All rights reserved. +// + +#import "MRPlaylistRowView.h" + +@interface MRPlaylistRowView () + +@property (nonatomic, weak) NSTextField *titleLb; +@property (nonatomic, weak) NSTextField *detailLb; + +@end + +@implementation MRPlaylistRowData +@end + +@implementation MRPlaylistRowView + +- (NSTextField *)createLabel +{ + NSTextField *tx = [[NSTextField alloc] init]; + tx.editable = NO; + //点击的时候不显示蓝色外框 + tx.focusRingType = NSFocusRingTypeNone; + tx.bordered = NO; + tx.backgroundColor = [NSColor clearColor]; + tx.font = [NSFont systemFontOfSize:14]; + tx.usesSingleLineMode = YES; + tx.lineBreakMode = NSLineBreakByTruncatingMiddle; + tx.textColor = [NSColor whiteColor]; + return tx; +} + +- (instancetype)initWithFrame:(NSRect)frameRect +{ + self = [super initWithFrame:frameRect]; + if (self) { + + [self setWantsLayer:YES]; + + NSTextField *titleLb = [self createLabel]; + NSTextField *detailLB = [self createLabel]; + detailLB.alignment = NSTextAlignmentRight; + + [self addSubview:titleLb]; + [self addSubview:detailLB]; + + self.titleLb = titleLb; + self.detailLb = detailLB; + } + return self; +} + +- (void)updateTitle:(NSString *)title +{ + if (!title) { + title = @""; + } + self.titleLb.stringValue = title; +} + +- (void)updateDetail:(NSString *)title +{ + if (!title) { + title = @""; + } + self.detailLb.stringValue = title; +} + +- (void)layout +{ + CGRect frameRect = self.bounds; + CGFloat padding = 6; + + CGFloat minX = padding; + CGFloat maxX = CGRectGetWidth(frameRect) - padding; + CGFloat height = CGRectGetHeight(frameRect); + + { + NSRect rect = self.bounds; + rect.size.width = (maxX - minX)/2.0; + CGSize labelSize = [self.titleLb sizeThatFits:rect.size]; + rect.origin.x = minX; + rect.origin.y = (height - labelSize.height)/2.0; + rect.size = labelSize; + self.titleLb.frame = rect; + minX = CGRectGetMaxX(rect); + minX += padding; + } + + { + NSRect rect = self.bounds; + rect.size.width = maxX - minX; + CGSize labelSize = [self.detailLb sizeThatFits:rect.size]; + rect.origin.x = minX; + rect.origin.y = (height - labelSize.height)/2.0; + rect.size.height = labelSize.height; + self.detailLb.frame = rect; + } +} + +//如果放在drawSeparatorInRect画,必须设置gridStyleMask不为None,但是多余空白行的线会顶到头 +- (void)drawBackgroundInRect:(NSRect)dirtyRect +{ + switch (self.sepStyle) { + case KSeparactorStyleFull: + case KSeparactorStyleHeadPadding: + { + CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; + CGContextSetStrokeColorWithColor(context, [[NSColor colorWithWhite:0.1 alpha:1] CGColor]); + CGContextSetLineWidth(context, 1); + // CGFloat dashArray[] = {3,1}; + // CGContextSetLineDash(context, 1, dashArray, 1);//跳过3个再画虚线,所以刚开始有6-(3-2)=5个虚点 + CGFloat y = CGRectGetHeight(dirtyRect); + if (self.sepStyle == KSeparactorStyleFull) { + CGContextMoveToPoint(context, 0, y); + } else { + CGContextMoveToPoint(context, 15, y); + } + + CGContextAddLineToPoint(context, CGRectGetWidth(dirtyRect), y); + CGContextStrokePath(context); + } + break; + case KSeparactorStyleNone: + { + + } + break; + } +} + +- (void)updateData:(MRPlaylistRowData *)data +{ + [self updateTitle:data.key]; + [self updateDetail:data.value]; +} + +@end diff --git a/examples/macos/IJKMediaDemo/MRPlaylistViewController.h b/examples/macos/IJKMediaDemo/MRPlaylistViewController.h new file mode 100644 index 0000000000..19395dea07 --- /dev/null +++ b/examples/macos/IJKMediaDemo/MRPlaylistViewController.h @@ -0,0 +1,17 @@ +// +// MRPlaylistViewController.h +// IJKMediaMacDemo +// +// Created by Reach Matt on 2024/1/24. +// Copyright © 2024 IJK Mac. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MRPlaylistViewController : NSViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/macos/IJKMediaDemo/MRPlaylistViewController.m b/examples/macos/IJKMediaDemo/MRPlaylistViewController.m new file mode 100644 index 0000000000..1853f8e2e4 --- /dev/null +++ b/examples/macos/IJKMediaDemo/MRPlaylistViewController.m @@ -0,0 +1,57 @@ +// +// MRPlaylistViewController.m +// IJKMediaMacDemo +// +// Created by Reach Matt on 2024/1/24. +// Copyright © 2024 IJK Mac. All rights reserved. +// + +#import "MRPlaylistViewController.h" +#import "MRPlaylistRowView.h" + +@interface MRPlaylistViewController () + +@property (nonatomic, strong) NSMutableArray *hudDataArray; + +@end + +@implementation MRPlaylistViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do view setup here. +} + +- (NSMutableArray *)hudDataArray +{ + if (!_hudDataArray) { + _hudDataArray = [NSMutableArray array]; + } + return _hudDataArray; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return [self.hudDataArray count]; +} + +- (nullable NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row +{ + return nil; +} + +- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row +{ + MRPlaylistRowView *rowView = [tableView makeViewWithIdentifier:@"Row" owner:self]; + if (rowView == nil) { + rowView = [[MRPlaylistRowView alloc]init]; + rowView.identifier = @"Row"; + } + if (row < [self.hudDataArray count]) { + MRPlaylistRowData *data = [self.hudDataArray objectAtIndex:row]; + [rowView updateData:data]; + } + + return rowView; +} +@end diff --git a/examples/macos/IJKMediaDemo/MRPlaylistViewController.xib b/examples/macos/IJKMediaDemo/MRPlaylistViewController.xib new file mode 100644 index 0000000000..145b2039af --- /dev/null +++ b/examples/macos/IJKMediaDemo/MRPlaylistViewController.xib @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/macos/IJKMediaDemo/MRRenderViewAuxProxy.m b/examples/macos/IJKMediaDemo/MRRenderViewAuxProxy.m index 27639f1f97..9179f49708 100644 --- a/examples/macos/IJKMediaDemo/MRRenderViewAuxProxy.m +++ b/examples/macos/IJKMediaDemo/MRRenderViewAuxProxy.m @@ -28,8 +28,6 @@ @implementation MRRenderViewAuxProxy @synthesize scalingMode = _scalingMode; -@synthesize subtitlePreference = _subtitlePreference; - @synthesize showHdrAnimation = _showHdrAnimation; - (instancetype)initWithFrame:(NSRect)frameRect @@ -91,6 +89,11 @@ - (CGImageRef)snapshot:(IJKSDLSnapshotType)aType return [view snapshot:aType]; } +- (id)context +{ + return nil; +} + - (void)setColorPreference:(IJKSDLColorConversionPreference)colorPreference { _colorPreference = colorPreference; @@ -156,17 +159,4 @@ - (void)setScalingMode:(IJKMPMovieScalingMode)scalingMode } } -- (void)setSubtitlePreference:(IJKSDLSubtitlePreference)subtitlePreference -{ - _subtitlePreference = subtitlePreference; - - [self.lock lock]; - NSArray *renderViewArr = [self.renderViewArr copy]; - [self.lock unlock]; - - for (NSView *view in renderViewArr) { - [view setSubtitlePreference:subtitlePreference]; - }; -} - @end diff --git a/examples/macos/IJKMediaDemo/MRRootViewController.m b/examples/macos/IJKMediaDemo/MRRootViewController.m index 9a36f83760..921eb6746f 100644 --- a/examples/macos/IJKMediaDemo/MRRootViewController.m +++ b/examples/macos/IJKMediaDemo/MRRootViewController.m @@ -20,41 +20,29 @@ #import "MRBaseView.h" #import "MultiRenderSample.h" #import "NSString+Ex.h" +#import "MRPlayerSettingsViewController.h" +#import "MRPlaylistViewController.h" +#import "MRCocoaBindingUserDefault.h" static NSString* lastPlayedKey = @"__lastPlayedKey"; static BOOL hdrAnimationShown = 0; @interface MRRootViewController () -@property (nonatomic, weak) IBOutlet NSStackView *advancedView; -@property (nonatomic, weak) IBOutlet MRBaseView *playerCtrlPanel; +@property (nonatomic, weak) IBOutlet NSView *playerContainer; +@property (nonatomic, weak) IBOutlet NSView *siderBarContainer; +@property (weak) IBOutlet NSLayoutConstraint *siderBarWidthConstraint; + +@property (nonatomic, weak) IBOutlet NSView *playerCtrlPanel; + @property (nonatomic, weak) IBOutlet NSTextField *playedTimeLb; @property (nonatomic, weak) IBOutlet NSTextField *durationTimeLb; @property (nonatomic, weak) IBOutlet NSButton *playCtrlBtn; @property (nonatomic, weak) IBOutlet MRProgressIndicator *playerSlider; -@property (nonatomic, weak) IBOutlet NSPopUpButton *subtitlePopUpBtn; -@property (nonatomic, weak) IBOutlet NSPopUpButton *audioPopUpBtn; -@property (nonatomic, weak) IBOutlet NSPopUpButton *videoPopUpBtn; @property (nonatomic, weak) IBOutlet NSTextField *seekCostLb; @property (nonatomic, weak) NSTrackingArea *trackingArea; -//for cocoa binding begin -@property (nonatomic, assign) float volume; -@property (nonatomic, assign) float subtitleDelay; -@property (nonatomic, assign) float subtitleMargin; - -@property (nonatomic, assign) float brightness; -@property (nonatomic, assign) float saturation; -@property (nonatomic, assign) float contrast; -@property (nonatomic, assign) BOOL use_openGL; -@property (nonatomic, copy) NSString *fcc; -@property (nonatomic, assign) int snapshot; -@property (nonatomic, assign) BOOL shouldShowHudView; -@property (nonatomic, assign) BOOL accurateSeek; -@property (nonatomic, assign) BOOL loop; -//for cocoa binding end - @property (nonatomic, assign) BOOL seeking; @property (nonatomic, weak) id eventMonitor; @@ -69,6 +57,12 @@ @interface MRRootViewController () 0) { + + __weakSelf__ + [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { + context.duration = 0.35; + context.allowsImplicitAnimation = YES; + context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + __strongSelf__ + [self.siderBarContainer.animator layoutSubtreeIfNeeded]; + self.siderBarWidthConstraint.animator.constant = 0; + [self.siderBarContainer.animator setNeedsLayout:YES]; + }]; + } else { + MRPlayerSettingsViewController *settings = [self findSettingViewController]; + BOOL created = NO; + if (!settings) { + settings = [[MRPlayerSettingsViewController alloc] initWithNibName:@"MRPlayerSettingsViewController" bundle:nil]; + __weakSelf__ + [settings onCloseCurrentStream:^(NSString * _Nonnull st) { + __strongSelf__ + [self.player closeCurrentStream:st]; + }]; + + [settings onExchangeSelectedStream:^(int idx) { + __strongSelf__ + [self.player exchangeSelectedStream:idx]; + }]; + + [settings onCaptureShot:^{ + __strongSelf__ + [self onCaptureShot]; + }]; + + created = YES; + [self addChildViewController:settings]; + } + [self.siderBarContainer addSubview:settings.view]; + CGRect frame = settings.view.bounds; + frame.size = CGSizeMake(frame.size.width, self.siderBarContainer.bounds.size.height); + settings.view.frame = frame; + + if (created) { + [self updateStreams]; + } + + __weakSelf__ + [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { + context.duration = 0.35; + context.allowsImplicitAnimation = YES; + context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + __strongSelf__ + [self.siderBarContainer.animator layoutSubtreeIfNeeded]; + self.siderBarWidthConstraint.animator.constant = frame.size.width; + [self.siderBarContainer.animator setNeedsLayout:YES]; + }]; + } } - (void)toggleTitleBar:(BOOL)show @@ -366,7 +363,7 @@ - (void)keyDown:(NSEvent *)event break; case kVK_ANSI_B: { - [self toggleAdvancedViewShow]; + } break; case kVK_ANSI_R: @@ -404,7 +401,7 @@ - (void)keyDown:(NSEvent *)event break; case kVK_ANSI_S: { - [self onCaptureShot:nil]; + [self onCaptureShot]; } break; case kVK_ANSI_Period: @@ -415,7 +412,7 @@ - (void)keyDown:(NSEvent *)event case kVK_ANSI_H: { if (event.modifierFlags & NSEventModifierFlagShift) { - [self toggleHUD:nil]; + [self onToggleHUD:nil]; } } break; @@ -462,16 +459,7 @@ - (void)keyDown:(NSEvent *)event case kVK_ANSI_S: { //loop exchange subtitles - NSInteger idx = [self.subtitlePopUpBtn indexOfSelectedItem]; - idx ++; - if (idx >= [self.subtitlePopUpBtn numberOfItems]) { - idx = 0; - } - NSMenuItem *item = [self.subtitlePopUpBtn itemAtIndex:idx]; - if (item) { - [self.subtitlePopUpBtn selectItem:item]; - [self.subtitlePopUpBtn.target performSelector:self.subtitlePopUpBtn.action withObject:self.subtitlePopUpBtn]; - } +#warning TODO exchangeToNextSubtitle } break; } @@ -489,23 +477,23 @@ - (void)keyDown:(NSEvent *)event break; case kVK_DownArrow: { - float volume = self.volume; + float volume = [MRCocoaBindingUserDefault volume]; volume -= 0.1; if (volume < 0) { volume = .0f; } - self.volume = volume; + [MRCocoaBindingUserDefault setVolume:volume]; [self onVolumeChange:nil]; } break; case kVK_UpArrow: { - float volume = self.volume; + float volume = [MRCocoaBindingUserDefault volume]; volume += 0.1; if (volume > 1) { volume = 1.0f; } - self.volume = volume; + [MRCocoaBindingUserDefault setValue:@(volume) forKey:@"volume"]; [self onVolumeChange:nil]; } break; @@ -516,22 +504,22 @@ - (void)keyDown:(NSEvent *)event break; case kVK_ANSI_Minus: { - if (self.player) { - float delay = [self.player currentSubtitleExtraDelay]; - delay -= 2; - self.subtitleDelay = delay; - [self.player updateSubtitleExtraDelay:delay]; - } +// if (self.player) { +// float delay = [self.player currentSubtitleExtraDelay]; +// delay -= 2; +// self.subtitleDelay = delay; +// [self.player updateSubtitleExtraDelay:delay]; +// } } break; case kVK_ANSI_Equal: { - if (self.player) { - float delay = [self.player currentSubtitleExtraDelay]; - delay += 2; - self.subtitleDelay = delay; - [self.player updateSubtitleExtraDelay:delay]; - } +// if (self.player) { +// float delay = [self.player currentSubtitleExtraDelay]; +// delay += 2; +// self.subtitleDelay = delay; +// [self.player updateSubtitleExtraDelay:delay]; +// } } break; case kVK_Escape: @@ -578,9 +566,8 @@ - (void)perpareIJKPlayer:(NSURL *)url hwaccel:(BOOL)hwaccel if (self.playingUrl) { [self doStopPlay]; } - + self.playingUrl = url; - self.seeking = NO; IJKFFOptions *options = [IJKFFOptions optionsByDefault]; @@ -590,6 +577,7 @@ - (void)perpareIJKPlayer:(NSURL *)url hwaccel:(BOOL)hwaccel // [options setPlayerOptionIntValue:50000 forKey:@"min-frames"]; [options setPlayerOptionIntValue:119 forKey:@"max-fps"]; [options setPlayerOptionIntValue:self.loop?0:1 forKey:@"loop"]; +#warning todo de_interlace [options setCodecOptionIntValue:IJK_AVDISCARD_DEFAULT forKey:@"skip_loop_filter"]; //for mgeg-ts seek [options setFormatOptionIntValue:1 forKey:@"seek_flag_keyframe"]; @@ -603,7 +591,7 @@ - (void)perpareIJKPlayer:(NSURL *)url hwaccel:(BOOL)hwaccel // [options setPlayerOptionIntValue:1 forKey:@"an"]; // [options setPlayerOptionIntValue:1 forKey:@"nodisp"]; - [options setPlayerOptionIntValue:[MRUtil boolForKey:@"values.copy_hw_frame"] forKey:@"copy_hw_frame"]; + [options setPlayerOptionIntValue:[MRCocoaBindingUserDefault copy_hw_frame] forKey:@"copy_hw_frame"]; if ([url isFileURL]) { //图片不使用 cvpixelbufferpool NSString *ext = [[[url path] pathExtension] lowercaseString]; @@ -639,14 +627,38 @@ - (void)perpareIJKPlayer:(NSURL *)url hwaccel:(BOOL)hwaccel // [options setPlayerOptionValue:@"fcc-i420" forKey:@"overlay-format"]; // [options setPlayerOptionValue:@"fcc-nv12" forKey:@"overlay-format"]; - [options setPlayerOptionValue:self.fcc forKey:@"overlay-format"]; + //[options setPlayerOptionIntValue:1 forKey:@"subtitle-texture-reuse"]; + [options setPlayerOptionValue:[MRCocoaBindingUserDefault overlay_format] forKey:@"overlay-format"]; [options setPlayerOptionIntValue:hwaccel forKey:@"videotoolbox_hwaccel"]; - [options setPlayerOptionIntValue:self.accurateSeek forKey:@"enable-accurate-seek"]; + [options setPlayerOptionIntValue:[MRCocoaBindingUserDefault accurate_seek] forKey:@"enable-accurate-seek"]; [options setPlayerOptionIntValue:1500 forKey:@"accurate-seek-timeout"]; - - options.metalRenderer = !self.use_openGL; + options.metalRenderer = ![MRCocoaBindingUserDefault use_opengl]; options.showHudView = self.shouldShowHudView; + //默认不使用dns缓存,指定超时时间才会使用; + if ([MRCocoaBindingUserDefault use_dns_cache]) { + [options setFormatOptionIntValue:[MRCocoaBindingUserDefault dns_cache_period] * 1000 forKey:@"dns_cache_timeout"]; + [options setFormatOptionValue:@"connect_timeout,ijkapplication,addrinfo_one_by_one,addrinfo_timeout,dns_cache_timeout,fastopen,dns_cache_clear" forKey:@"seg_inherit_options"]; + } else { + [options setFormatOptionValue:@"ijkapplication" forKey:@"seg_inherit_options"]; + } + + //实际测试效果不好,容易导致域名解析失败,谨慎使用;没有fallback逻辑 + //决定dns的方式,大于0时使用tcp_getaddrinfo_nonblock方式 + //[options setFormatOptionIntValue:0 forKey:@"addrinfo_timeout"]; + //[options setFormatOptionIntValue:0 forKey:@"addrinfo_one_by_one"]; +// [options setFormatOptionIntValue:1 forKey:@"http_persistent"]; +// [options setFormatOptionValue:@"test=cookie" forKey:@"cookies"]; + //if you want set ts segments options only: +// [options setFormatOptionValue:@"fastopen=2:dns_cache_timeout=600000:addrinfo_timeout=2000000" forKey:@"seg_format_options"]; + //default inherit options : "headers", "user_agent", "cookies", "http_proxy", "referer", "rw_timeout", "icy",you can inherit more: + + if ([MRCocoaBindingUserDefault open_gzip]) { + [options setFormatOptionValue:@"Accept-Encoding: gzip, deflate" forKey:@"headers"]; + } + //protocol_whitelist need add httpproxy + //[options setFormatOptionValue:@"http://10.7.36.42:8888" forKey:@"http_proxy"]; + NSMutableArray *dus = [NSMutableArray array]; if ([url.scheme isEqualToString:@"file"] && [url.absoluteString.pathExtension isEqualToString:@"m3u8"]) { NSString *str = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; @@ -671,11 +683,9 @@ - (void)perpareIJKPlayer:(NSURL *)url hwaccel:(BOOL)hwaccel self.player = [[IJKFFMoviePlayerController alloc] initWithContentURL:url withOptions:options]; NSView *playerView = self.player.view; - CGRect rect = self.view.frame; - rect.origin = CGPointZero; - playerView.frame = rect; + playerView.frame = self.playerContainer.bounds; playerView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; - [self.view addSubview:playerView positioned:NSWindowBelow relativeTo:nil]; + [self.playerContainer addSubview:playerView positioned:NSWindowBelow relativeTo:self.playerCtrlPanel]; playerView.showHdrAnimation = !hdrAnimationShown; //playerView.preventDisplay = YES; @@ -684,9 +694,9 @@ - (void)perpareIJKPlayer:(NSURL *)url hwaccel:(BOOL)hwaccel [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ijkPlayerFirstVideoFrameRendered:) name:IJKMPMoviePlayerFirstVideoFrameRenderedNotification object:self.player]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ijkPlayerSelectedStreamDidChange:) name:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification object:self.player]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ijkPlayerPreparedToPlay:) name:IJKMPMediaPlaybackIsPreparedToPlayDidChangeNotification object:self.player]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ijkPlayerPreparedToPlay:) name:IJKMPMoviePlayerSelectedStreamDidChangeNotification object:self.player]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ijkPlayerSelectedStreamDidChange:) name:IJKMPMoviePlayerSelectedStreamDidChangeNotification object:self.player]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ijkPlayerDidFinish:) name:IJKMPMoviePlayerPlaybackDidFinishNotification object:self.player]; @@ -698,13 +708,19 @@ - (void)perpareIJKPlayer:(NSURL *)url hwaccel:(BOOL)hwaccel [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ijkPlayerVideoDecoderFatal:) name:IJKMPMoviePlayerVideoDecoderFatalNotification object:self.player]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ijkPlayerRecvWarning:) name:IJKMPMoviePlayerPlaybackRecvWarningNotification object:self.player]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ijkPlayerHdrAnimationStateChanged:) name:IJKMoviePlayerHDRAnimationStateChanged object:self.player.view]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ijkPlayerSelectingStreamDidFailed:) name:IJKMoviePlayerSelectingStreamDidFailed object:self.player]; + self.player.shouldAutoplay = YES; [self onVolumeChange:nil]; + [self applyScalingMode]; + [self applyDAR]; + [self applyRotate]; + [self applyBSC]; + [self applySubtitlePreference]; } #pragma mark - ijkplayer @@ -735,6 +751,15 @@ - (void)ijkPlayerHdrAnimationStateChanged:(NSNotification *)notifi } } +- (void)ijkPlayerSelectingStreamDidFailed:(NSNotification *)notifi +{ + if (self.player == notifi.object) { + int stream = [notifi.userInfo[IJKMoviePlayerSelectingStreamIDUserInfoKey] intValue]; + int code = [notifi.userInfo[IJKMoviePlayerSelectingStreamErrUserInfoKey] intValue]; + NSLog(@"Selecting Stream Did Failed:%d,%d",stream,code); + } +} + - (void)ijkPlayerFirstVideoFrameRendered:(NSNotification *)notifi { if (self.player == notifi.object) { @@ -774,43 +799,36 @@ - (void)ijkPlayerCouldNotFindCodec:(NSNotification *)notifi NSLog(@"找不到解码器,联系开发小帅锅:%@",notifi.userInfo); } +- (void)applyLockScreenRatio +{ + const CGSize videoSize = self.player.naturalSize; + if (CGSizeEqualToSize(CGSizeZero, videoSize)) { + return; + } + const CGRect screenVisibleFrame = self.view.window.screen.visibleFrame; + const CGSize screenSize = screenVisibleFrame.size; + CGSize targetSize = videoSize; + + if (videoSize.width > screenSize.width || videoSize.height > screenSize.height) { + float wRatio = screenSize.width / videoSize.width; + float hRatio = screenSize.height / videoSize.height; + float ratio = MIN(wRatio, hRatio); + targetSize = CGSizeMake(floor(videoSize.width * ratio), floor(videoSize.height * ratio)); + } + [self.view.window setAspectRatio:targetSize]; + + CGRect targetRect = CGRectMake(screenVisibleFrame.origin.x + (screenSize.width - targetSize.width) / 2.0, screenVisibleFrame.origin.y + (screenSize.height - targetSize.height) / 2.0, targetSize.width, targetSize.height); + + NSLog(@"窗口位置:%@;视频尺寸:%@",NSStringFromRect(targetRect),NSStringFromSize(videoSize)); + [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { + [self.view.window.animator setFrame:targetRect display:YES]; + }]; +} - (void)ijkPlayerNaturalSizeAvailable:(NSNotification *)notifi { -// if (self.player == notifi.object) { -// CGSize const videoSize = NSSizeFromString(notifi.userInfo[@"size"]); -// if (!CGSizeEqualToSize(self.view.window.aspectRatio, videoSize)) { -// -//// [self.view.window setAspectRatio:videoSize]; -// CGRect rect = self.view.window.frame; -// -// CGPoint center = CGPointMake(rect.origin.x + rect.size.width/2.0, rect.origin.y + rect.size.height/2.0); -// static float kMaxRatio = 1.0; -// if (videoSize.width < videoSize.height) { -// rect.size.width = rect.size.height / videoSize.height * videoSize.width; -// if (rect.size.width > [[[NSScreen screens] firstObject]frame].size.width * kMaxRatio) { -// float ratio = [[[NSScreen screens] firstObject]frame].size.width * kMaxRatio / rect.size.width; -// rect.size.width *= ratio; -// rect.size.height *= ratio; -// } -// } else { -// rect.size.height = rect.size.width / videoSize.width * videoSize.height; -// if (rect.size.height > [[[NSScreen screens] firstObject]frame].size.height * kMaxRatio) { -// float ratio = [[[NSScreen screens] firstObject]frame].size.height * kMaxRatio / rect.size.height; -// rect.size.width *= ratio; -// rect.size.height *= ratio; -// } -// } -// //keep center. -// rect.origin = CGPointMake(center.x - rect.size.width/2.0, center.y - rect.size.height/2.0); -// rect.size = CGSizeMake((int)rect.size.width, (int)rect.size.height); -// NSLog(@"窗口位置:%@;视频尺寸:%@",NSStringFromRect(rect),NSStringFromSize(videoSize)); -// [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { -// [self.view.window.animator setFrame:rect display:YES]; -// [self.view.window.animator center]; -// }]; -// -// } -// } + if (self.player == notifi.object && [MRCocoaBindingUserDefault lock_screen_ratio]) { + [self applyLockScreenRatio]; + } } - (void)ijkPlayerDidFinish:(NSNotification *)notifi @@ -856,7 +874,7 @@ - (void)ijkPlayerDidFinish:(NSNotification *)notifi } else { NSString *key = [[self.playingUrl absoluteString] md5Hash]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:key]; -// self.playingUrl = nil; + self.playingUrl = nil; [self playNext:nil]; } } @@ -890,84 +908,12 @@ - (NSTimeInterval)readCurrentPlayRecord - (void)updateStreams { if (self.player.isPreparedToPlay) { - NSDictionary *dic = self.player.monitor.mediaMeta; - int audioIdx = [dic[k_IJKM_VAL_TYPE__AUDIO] intValue]; - NSLog(@"当前音频:%d",audioIdx); - int videoIdx = [dic[k_IJKM_VAL_TYPE__VIDEO] intValue]; - NSLog(@"当前视频:%d",videoIdx); - int subtitleIdx = [dic[k_IJKM_VAL_TYPE__SUBTITLE] intValue]; - NSLog(@"当前字幕:%d",subtitleIdx); - - [self.subtitlePopUpBtn removeAllItems]; - NSString *currentTitle = @"选择字幕"; - [self.subtitlePopUpBtn addItemWithTitle:currentTitle]; - - [self.audioPopUpBtn removeAllItems]; - NSString *currentAudio = @"选择音轨"; - [self.audioPopUpBtn addItemWithTitle:currentAudio]; - - [self.videoPopUpBtn removeAllItems]; - NSString *currentVideo = @"选择视轨"; - [self.videoPopUpBtn addItemWithTitle:currentVideo]; - - for (NSDictionary *stream in dic[kk_IJKM_KEY_STREAMS]) { - NSString *type = stream[k_IJKM_KEY_TYPE]; - int streamIdx = [stream[k_IJKM_KEY_STREAM_IDX] intValue]; - if ([type isEqualToString:k_IJKM_VAL_TYPE__SUBTITLE]) { - NSLog(@"subtile meta:%@",stream); - NSString *url = stream[k_IJKM_KEY_EX_SUBTITLE_URL]; - NSString *title = nil; - if (url) { - title = [[url lastPathComponent] stringByRemovingPercentEncoding]; - } else { - title = stream[k_IJKM_KEY_TITLE]; - if (title.length == 0) { - title = stream[k_IJKM_KEY_LANGUAGE]; - } - if (title.length == 0) { - title = @"未知"; - } - } - title = [NSString stringWithFormat:@"%@-%d",title,streamIdx]; - if ([dic[k_IJKM_VAL_TYPE__SUBTITLE] intValue] == streamIdx) { - currentTitle = title; - } - [self.subtitlePopUpBtn addItemWithTitle:title]; - } else if ([type isEqualToString:k_IJKM_VAL_TYPE__AUDIO]) { - NSLog(@"audio meta:%@",stream); - NSString *title = stream[k_IJKM_KEY_TITLE]; - if (title.length == 0) { - title = stream[k_IJKM_KEY_LANGUAGE]; - } - if (title.length == 0) { - title = @"未知"; - } - title = [NSString stringWithFormat:@"%@-%d",title,streamIdx]; - if ([dic[k_IJKM_VAL_TYPE__AUDIO] intValue] == streamIdx) { - currentAudio = title; - } - [self.audioPopUpBtn addItemWithTitle:title]; - } else if ([type isEqualToString:k_IJKM_VAL_TYPE__VIDEO]) { - NSLog(@"video meta:%@",stream); - NSString *title = stream[k_IJKM_KEY_TITLE]; - if (title.length == 0) { - title = stream[k_IJKM_KEY_LANGUAGE]; - } - if (title.length == 0) { - title = @"未知"; - } - title = [NSString stringWithFormat:@"%@-%d",title,streamIdx]; - if ([dic[k_IJKM_VAL_TYPE__VIDEO] intValue] == streamIdx) { - currentVideo = title; - } - [self.videoPopUpBtn addItemWithTitle:title]; - } - } - [self.subtitlePopUpBtn selectItemWithTitle:currentTitle]; - [self.audioPopUpBtn selectItemWithTitle:currentAudio]; - [self.videoPopUpBtn selectItemWithTitle:currentVideo]; + NSArray *chapteArr = self.player.monitor.chapterMetaArr; + NSLog(@"video chapters:%@",chapteArr); + MRPlayerSettingsViewController *settings = [self findSettingViewController]; + [settings updateTracks:dic]; if (!self.tickTimer) { self.tickTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(onTick:) userInfo:nil repeats:YES]; } @@ -1000,15 +946,8 @@ - (void)playURL:(NSURL *)url NSString *title = [NSString stringWithFormat:@"(%ld/%ld)%@",(long)idx,[[self playList] count],videoName]; [self.view.window setTitle:title]; - [self onReset:nil]; self.playCtrlBtn.state = NSControlStateValueOn; - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; - p.bottomMargin = self.subtitleMargin; - NSNumber *number = [[NSUserDefaultsController sharedUserDefaultsController] valueForKeyPath:@"values.subtitleFontRatio"]; - p.ratio = [number floatValue]; - self.player.view.subtitlePreference = p; - int startTime = (int)([self readCurrentPlayRecord] * 1000); [self.player setPlayerOptionIntValue:startTime forKey:@"seek-at-start"]; [self.player prepareToPlay]; @@ -1060,6 +999,63 @@ - (NSURL *)existTaskForUrl:(NSURL *)url return t; } ++ (NSArray *)parseXPlayList:(NSURL*)url +{ + NSString *str = [[NSString alloc] initWithContentsOfFile:[url path] encoding:NSUTF8StringEncoding error:nil]; + str = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSArray *lines = [str componentsSeparatedByString:@"\n"]; + NSMutableArray *preLines = [NSMutableArray array]; + int begin = -1; + int end = -1; + + for (int i = 0; i < lines.count; i++) { + NSString *path = lines[i]; + if (!path || [path length] == 0) { + continue; + } else if ([path hasPrefix:@"#"]) { + continue; + } else if ([path hasPrefix:@"--break"]) { + break; + } else if ([path hasPrefix:@"--begin"]) { + begin = (int)preLines.count; + continue; + } else if ([path hasPrefix:@"--end"]) { + end = (int)preLines.count; + continue; + } + [preLines addObject:path]; + } + + if (begin == -1) { + begin = 0; + } + if (end == -1) { + end = (int)[preLines count] - 1; + } + if (begin >= end) { + NSLog(@"请检查XList文件里的begin位置"); + return nil; + } + NSArray *preLines2 = [preLines subarrayWithRange:NSMakeRange(begin, end - begin)]; + NSMutableArray *playList = [NSMutableArray array]; + for (int i = 0; i < preLines2.count; i++) { + NSString *path = preLines2[i]; + if (!path || [path length] == 0) { + continue; + } + if ([path hasPrefix:@"#"]) { + continue; + } + if ([path hasPrefix:@"--break"]) { + break; + } + NSURL *url = [NSURL URLWithString:path]; + [playList addObject:url]; + } + NSLog(@"从XList读取到:%lu个视频文件",(unsigned long)playList.count); + return [playList copy]; +} + - (void)appendToPlayList:(NSArray *)bookmarkArr reset:(BOOL)reset { NSMutableArray *videos = [NSMutableArray array]; @@ -1071,6 +1067,14 @@ - (void)appendToPlayList:(NSArray *)bookmarkArr reset:(BOOL)reset if ([self existTaskForUrl:url]) { continue; } + if ([[[url pathExtension] lowercaseString] isEqualToString:@"xlist"]) { + if (reset) { + [self.playList removeAllObjects]; + } + [self.playList addObjectsFromArray:[[self class] parseXPlayList:url]]; + [self playFirstIfNeed]; + continue; + } if ([dic[@"type"] intValue] == 0) { [videos addObject:url]; } else if ([dic[@"type"] intValue] == 1) { @@ -1137,16 +1141,12 @@ - (NSDragOperation)acceptDragOperation:(NSArray *)list BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDirectory]; if (isExist) { if (isDirectory) { - //扫描文件夹 -// NSString *dir = [url path]; -// NSArray *dicArr = [MRUtil scanFolderWithPath:dir filter:[MRUtil acceptMediaType]]; -// if ([dicArr count] > 0) { -// return NSDragOperationCopy; -// } return NSDragOperationCopy; } else { NSString *pathExtension = [[url pathExtension] lowercaseString]; - if ([[MRUtil acceptMediaType] containsObject:pathExtension]) { + if ([@"xlist" isEqualToString:pathExtension]) { + return NSDragOperationCopy; + } else if ([[MRUtil acceptMediaType] containsObject:pathExtension]) { return NSDragOperationCopy; } } @@ -1188,20 +1188,20 @@ - (IBAction)pauseOrPlay:(NSButton *)sender } } -- (IBAction)toggleHUD:(id)sender +- (IBAction)onToggleHUD:(id)sender { self.shouldShowHudView = !self.shouldShowHudView; self.player.shouldShowHudView = self.shouldShowHudView; } -- (IBAction)onMoreFunc:(id)sender +- (IBAction)onToggleSiderBar:(id)sender { - [self toggleAdvancedViewShow]; + [self showPlayerSettingsSideBar]; } - (BOOL)preferHW { - return [MRUtil boolForKey:@"values.hw"]; + return [MRCocoaBindingUserDefault use_hw]; } - (void)retry @@ -1321,27 +1321,27 @@ - (void)seekTo:(float)cp } } -- (IBAction)fastRewind:(NSButton *)sender +- (void)fastRewind:(NSButton *)sender { float cp = self.player.currentPlaybackTime; - cp -= 10; + cp -= [MRCocoaBindingUserDefault seek_step]; [self seekTo:cp]; } -- (IBAction)fastForward:(NSButton *)sender -{ +- (void)fastForward:(NSButton *)sender +{ if (self.player.playbackState == IJKMPMoviePlaybackStatePaused) { [self.player stepToNextFrame]; } else { float cp = self.player.currentPlaybackTime; - cp += 10; + cp += [MRCocoaBindingUserDefault seek_step]; [self seekTo:cp]; } } - (IBAction)onVolumeChange:(NSSlider *)sender { - self.player.playbackVolume = self.volume; + self.player.playbackVolume = [MRCocoaBindingUserDefault volume]; } #pragma mark 倍速设置 @@ -1355,116 +1355,260 @@ - (void)updateSpeed:(NSButton *)sender #pragma mark 字幕设置 -- (IBAction)onChangeSubtitleColor:(NSPopUpButton *)sender -{ - NSMenuItem *item = [sender selectedItem]; - int bgrValue = (int)item.tag; - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; - p.color = bgrValue; - self.player.view.subtitlePreference = p; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } -} - -- (IBAction)onChangeSubtitleSize:(NSStepper *)sender +- (IBAction)onChangeSubtitleDelay:(NSStepper *)sender { - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; - p.ratio = sender.floatValue; - self.player.view.subtitlePreference = p; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } + float delay = sender.floatValue; + [self.player updateSubtitleExtraDelay:delay]; } -- (IBAction)onSelectSubtitle:(NSPopUpButton*)sender +- (void)applySubtitlePreference { - if (sender.indexOfSelectedItem == 0) { - [self.player closeCurrentStream:k_IJKM_VAL_TYPE__SUBTITLE]; + IJKSDLSubtitlePreference p = self.player.subtitlePreference; + p.color = color2int([MRCocoaBindingUserDefault subtitle_text_color]); + p.bgColor = color2int([MRCocoaBindingUserDefault subtitle_bg_color]); + p.strokeColor = color2int([MRCocoaBindingUserDefault subtitle_stroke_color]); + p.strokeSize = [MRCocoaBindingUserDefault subtitle_stroke_size]; + p.bottomMargin = ([MRCocoaBindingUserDefault subtitle_bottom_margin] - 20) / 100.0; + p.scale = [MRCocoaBindingUserDefault subtitle_scale]; + NSString *name = [MRCocoaBindingUserDefault subtitle_font_name]; + if (name) { + strcpy(p.name,[name UTF8String]); } else { - NSString *title = sender.selectedItem.title; - NSArray *items = [title componentsSeparatedByString:@"-"]; - int idx = [[items lastObject] intValue]; - NSLog(@"SelectSubtitleTrack:%d",idx); - [self.player exchangeSelectedStream:idx]; + bzero(p.name, sizeof(p.name)); } + self.player.subtitlePreference = p; } -- (IBAction)onChangeSubtitleDelay:(NSStepper *)sender -{ - float delay = sender.floatValue; - [self.player updateSubtitleExtraDelay:delay]; -} +#pragma mark 色彩调节 -- (IBAction)onChangeSubtitleBottomMargin:(NSSlider *)sender +- (void)applyBSC { - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; - p.bottomMargin = sender.floatValue; - self.player.view.subtitlePreference = p; + IJKSDLColorConversionPreference colorPreference = self.player.view.colorPreference; + colorPreference.brightness = [MRCocoaBindingUserDefault color_adjust_brightness]; + colorPreference.saturation = [MRCocoaBindingUserDefault color_adjust_saturation]; + colorPreference.contrast = [MRCocoaBindingUserDefault color_adjust_contrast]; + + self.player.view.colorPreference = colorPreference; if (!self.player.isPlaying) { [self.player.view setNeedsRefreshCurrentPic]; } } -#pragma mark 画面设置 +#pragma mark 播放器偏好设置 -- (IBAction)onChangeRenderType:(NSPopUpButton *)sender +- (void)applyScalingMode { - [self retry]; + [self.player setScalingMode:[MRCocoaBindingUserDefault picture_fill_mode]]; } -- (IBAction)onChangeScaleMode:(NSPopUpButton *)sender +- (void)applyDAR { - NSMenuItem *item = [sender selectedItem]; - if (item.tag == 1) { - //scale to fill - [self.player setScalingMode:IJKMPMovieScalingModeFill]; - } else if (item.tag == 2) { - //aspect fill - [self.player setScalingMode:IJKMPMovieScalingModeAspectFill]; - } else if (item.tag == 3) { - //aspect fit - [self.player setScalingMode:IJKMPMovieScalingModeAspectFit]; - } - - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; + int value = [MRCocoaBindingUserDefault picture_wh_ratio]; + int dar_num = 0; + int dar_den = 1; + if (value == 1) { + dar_num = 4; + dar_den = 3; + } else if (value == 2) { + dar_num = 16; + dar_den = 9; + } else if (value == 3) { + dar_num = 1; + dar_den = 1; } + self.player.view.darPreference = (IJKSDLDARPreference){1.0 * dar_num/dar_den}; } -- (IBAction)onRotate:(NSPopUpButton *)sender +- (void)applyRotate { - NSMenuItem *item = [sender selectedItem]; - IJKSDLRotatePreference preference = self.player.view.rotatePreference; - - if (item.tag == 0) { + int rotate = [MRCocoaBindingUserDefault picture_ratate_mode]; + if (rotate == 0) { preference.type = IJKSDLRotateNone; preference.degrees = 0; - } else if (item.tag == 1) { + } else if (rotate == 1) { preference.type = IJKSDLRotateZ; preference.degrees = -90; - } else if (item.tag == 2) { + } else if (rotate == 2) { preference.type = IJKSDLRotateZ; preference.degrees = -180; - } else if (item.tag == 3) { + } else if (rotate == 3) { preference.type = IJKSDLRotateZ; preference.degrees = -270; - } else if (item.tag == 4) { + } else if (rotate == 4) { preference.type = IJKSDLRotateY; preference.degrees = 180; - } else if (item.tag == 5) { + } else if (rotate == 5) { preference.type = IJKSDLRotateX; preference.degrees = 180; } - self.player.view.rotatePreference = preference; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } NSLog(@"rotate:%@ %d",@[@"None",@"X",@"Y",@"Z"][preference.type],(int)preference.degrees); } +- (void)observerCocoaBingsChange +{ + __weakSelf__ + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self applyBSC]; + } forKey:@"color_adjust_brightness"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self applyBSC]; + } forKey:@"color_adjust_saturation"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self applyBSC]; + } forKey:@"color_adjust_contrast"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + int value = [v intValue]; + [self.player setScalingMode:value]; + if (!self.player.isPlaying) { + [self.player.view setNeedsRefreshCurrentPic]; + } + } forKey:@"picture_fill_mode"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self applyDAR]; + if (!self.player.isPlaying) { + [self.player.view setNeedsRefreshCurrentPic]; + } + } forKey:@"picture_wh_ratio"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self applyRotate]; + if (!self.player.isPlaying) { + [self.player.view setNeedsRefreshCurrentPic]; + } + } forKey:@"picture_ratate_mode"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + IJKSDLSubtitlePreference p = self.player.subtitlePreference; + NSString *name = v; + if (name) { + strcpy(p.name,[name UTF8String]); + } else { + bzero(p.name, sizeof(p.name)); + } + self.player.subtitlePreference = p; + } forKey:@"subtitle_font_name"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + IJKSDLSubtitlePreference p = self.player.subtitlePreference; + p.bottomMargin = ([v intValue] - 20) / 100.0; + self.player.subtitlePreference = p; + } forKey:@"subtitle_bottom_margin"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + IJKSDLSubtitlePreference p = self.player.subtitlePreference; + p.scale = [v floatValue]; + self.player.subtitlePreference = p; + } forKey:@"subtitle_scale"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull rm) { + __strongSelf__ + NSColor *color = v; + IJKSDLSubtitlePreference p = self.player.subtitlePreference; + p.color = color2int(color); + self.player.subtitlePreference = p; + } forKey:@"subtitle_text_color"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull rm) { + __strongSelf__ + NSColor *color = v; + IJKSDLSubtitlePreference p = self.player.subtitlePreference; + p.bgColor = color2int(color); + self.player.subtitlePreference = p; + } forKey:@"subtitle_bg_color"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull rm) { + __strongSelf__ + NSColor *color = v; + IJKSDLSubtitlePreference p = self.player.subtitlePreference; + p.strokeColor = color2int(color); + self.player.subtitlePreference = p; + } forKey:@"subtitle_stroke_color"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull rm) { + __strongSelf__ + IJKSDLSubtitlePreference p = self.player.subtitlePreference; + p.strokeSize = [v intValue]; + self.player.subtitlePreference = p; + } forKey:@"subtitle_stroke_size"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self.player enableAccurateSeek:[v boolValue]]; + } forKey:@"accurate_seek"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + if ([v boolValue]) { + [self applyLockScreenRatio]; + } + } forKey:@"lock_screen_ratio"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self retry]; + } forKey:@"use_opengl"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self retry]; + } forKey:@"use_hw"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + if ([MRCocoaBindingUserDefault use_hw]) { + [self retry]; + } + } forKey:@"copy_hw_frame"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self retry]; + } forKey:@"de_interlace"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ +#warning todo + } forKey:@"open_hdr"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + if (![MRCocoaBindingUserDefault use_hw]) { + [self retry]; + } + } forKey:@"overlay_format"]; + +#warning todo open_gzip + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self retry]; + } forKey:@"open_gzip"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self retry]; + } forKey:@"use_dns_cache"]; + + [[MRCocoaBindingUserDefault sharedDefault] onChange:^(id _Nonnull v, BOOL * _Nonnull r) { + __strongSelf__ + [self retry]; + } forKey:@"dns_cache_period"]; +} + - (NSString *)saveDir:(NSString *)subDir { NSArray *subDirs = subDir ? @[@"ijkPro",subDir] : @[@"ijkPro"]; @@ -1484,9 +1628,9 @@ - (NSString *)dirForCurrentPlayingUrl return [self saveDir:[[self.playingUrl path] stringByDeletingLastPathComponent]]; } -- (IBAction)onCaptureShot:(id)sender +- (void)onCaptureShot { - CGImageRef img = [self.player.view snapshot:self.snapshot]; + CGImageRef img = [self.player.view snapshot:[MRCocoaBindingUserDefault snapshot_type]]; if (img) { NSString *dir = [self dirForCurrentPlayingUrl]; NSString *movieName = [self.playingUrl lastPathComponent]; @@ -1497,140 +1641,8 @@ - (IBAction)onCaptureShot:(id)sender } } -- (IBAction)onChangeBSC:(NSSlider *)sender -{ - if (sender.tag == 1) { - self.brightness = sender.floatValue; - } else if (sender.tag == 2) { - self.saturation = sender.floatValue; - } else if (sender.tag == 3) { - self.contrast = sender.floatValue; - } - - IJKSDLColorConversionPreference colorPreference = self.player.view.colorPreference; - colorPreference.brightness = self.brightness;//B - colorPreference.saturation = self.saturation;//S - colorPreference.contrast = self.contrast;//C - self.player.view.colorPreference = colorPreference; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } -} - -- (IBAction)onChangeDAR:(NSPopUpButton *)sender -{ - int dar_num = 0; - int dar_den = 1; - if (![sender.titleOfSelectedItem isEqual:@"还原"]) { - const char* str = sender.titleOfSelectedItem.UTF8String; - sscanf(str, "%d:%d", &dar_num, &dar_den); - } - self.player.view.darPreference = (IJKSDLDARPreference){1.0 * dar_num/dar_den}; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } -} - -- (IBAction)onReset:(NSButton *)sender -{ - if (sender.tag == 1) { - self.brightness = 1.0; - } else if (sender.tag == 2) { - self.saturation = 1.0; - } else if (sender.tag == 3) { - self.contrast = 1.0; - } else { - self.brightness = 1.0; - self.saturation = 1.0; - self.contrast = 1.0; - } - - [self onChangeBSC:nil]; -} - -#pragma mark 音轨设置 - -- (IBAction)onSelectAudioTrack:(NSPopUpButton*)sender -{ - if (sender.indexOfSelectedItem == 0) { - [self.player closeCurrentStream:k_IJKM_VAL_TYPE__AUDIO]; - } else { - NSString *title = sender.selectedItem.title; - NSArray *items = [title componentsSeparatedByString:@"-"]; - int idx = [[items lastObject] intValue]; - NSLog(@"SelectAudioTrack:%d",idx); - [self.player exchangeSelectedStream:idx]; - } -} - -- (IBAction)onSelectVideoTrack:(NSPopUpButton*)sender -{ - if (sender.indexOfSelectedItem == 0) { - [self.player closeCurrentStream:k_IJKM_VAL_TYPE__VIDEO]; - } else { - NSString *title = sender.selectedItem.title; - NSArray *items = [title componentsSeparatedByString:@"-"]; - int idx = [[items lastObject] intValue]; - NSLog(@"SelectVideoTrack:%d",idx); - [self.player exchangeSelectedStream:idx]; - } -} - #pragma mark 解码设置 -- (IBAction)onChangedHWaccel:(NSButton *)sender -{ - [self retry]; -} - -- (IBAction)onChangedAccurateSeek:(NSButton *)sender -{ - [self.player enableAccurateSeek:self.accurateSeek]; -} - -- (IBAction)onSelectFCC:(NSPopUpButton*)sender -{ - [self retry]; -} - -#pragma mark 日志级别 - -- (int)levelWithString:(NSString *)str -{ - str = [str lowercaseString]; - if ([str isEqualToString:@"default"]) { - return k_IJK_LOG_DEFAULT; - } else if ([str isEqualToString:@"verbose"]) { - return k_IJK_LOG_VERBOSE; - } else if ([str isEqualToString:@"debug"]) { - return k_IJK_LOG_DEBUG; - } else if ([str isEqualToString:@"info"]) { - return k_IJK_LOG_INFO; - } else if ([str isEqualToString:@"warn"]) { - return k_IJK_LOG_WARN; - } else if ([str isEqualToString:@"error"]) { - return k_IJK_LOG_ERROR; - } else if ([str isEqualToString:@"fatal"]) { - return k_IJK_LOG_FATAL; - } else if ([str isEqualToString:@"silent"]) { - return k_IJK_LOG_SILENT; - } else { - return k_IJK_LOG_UNKNOWN; - } -} - -- (void)reSetLoglevel:(NSString *)loglevel -{ - int level = [self levelWithString:loglevel]; - [IJKFFMoviePlayerController setLogLevel:level]; -} - -- (IBAction)onChangeLogLevel:(NSPopUpButton*)sender -{ - NSString *title = sender.selectedItem.title; - [self reSetLoglevel:title]; -} - - (IBAction)testMultiRenderSample:(NSButton *)sender { NSURL *playingUrl = self.playingUrl; diff --git a/examples/macos/IJKMediaDemo/MRRootViewController.xib b/examples/macos/IJKMediaDemo/MRRootViewController.xib index 4a80ca9f5e..1d50bb0c99 100644 --- a/examples/macos/IJKMediaDemo/MRRootViewController.xib +++ b/examples/macos/IJKMediaDemo/MRRootViewController.xib @@ -1,1008 +1,69 @@ - + - + + - - - - - - - - - - + + + + + + + + + - - + + - + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - + - - + + - + - + - - + + - - - + + + @@ -1010,73 +71,70 @@ - - - - - @@ -1101,32 +159,32 @@ - - + + - + - + - + - - + + - + - - + + - + - + @@ -1146,63 +204,62 @@ - - - - - + + + + - - - - - - + + + + - - - - + + + + + + + + + - - - - - - + + - - + - + + + - + - + diff --git a/examples/macos/IJKMediaDemo/MRStatisticalViewController.m b/examples/macos/IJKMediaDemo/MRStatisticalViewController.m index 3e7aa78834..2f17fe64d7 100644 --- a/examples/macos/IJKMediaDemo/MRStatisticalViewController.m +++ b/examples/macos/IJKMediaDemo/MRStatisticalViewController.m @@ -20,8 +20,8 @@ #import "MRBaseView.h" #import "MultiRenderSample.h" #import "NSString+Ex.h" +#import "MRCocoaBindingUserDefault.h" -static NSString* lastPlayedKey = @"__lastPlayedKey"; static BOOL hdrAnimationShown = 0; @interface MRStatisticalViewController () @@ -52,6 +52,7 @@ @interface MRStatisticalViewController () 0) { @@ -1001,6 +975,63 @@ - (NSURL *)existTaskForUrl:(NSURL *)url return t; } ++ (NSArray *)parseXPlayList:(NSURL*)url +{ + NSString *str = [[NSString alloc] initWithContentsOfFile:[url path] encoding:NSUTF8StringEncoding error:nil]; + NSArray *lines = [str componentsSeparatedByString:@"\n"]; + NSMutableArray *preLines = [NSMutableArray array]; + int begin = -1; + int end = -1; + + for (int i = 0; i < lines.count; i++) { + NSString *path = lines[i]; + if (!path || [path length] == 0) { + continue; + } else if ([path hasPrefix:@"#"]) { + continue; + } else if ([path hasPrefix:@"--break"]) { + break; + } else if ([path hasPrefix:@"--begin"]) { + begin = (int)preLines.count; + continue; + } else if ([path hasPrefix:@"--end"]) { + end = (int)preLines.count; + continue; + } + path = [path stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + [preLines addObject:path]; + } + + if (begin == -1) { + begin = 0; + } + if (end == -1) { + end = (int)[preLines count] - 1; + } + if (begin >= end) { + NSLog(@"请检查XList文件里的begin位置"); + return nil; + } + NSArray *preLines2 = [preLines subarrayWithRange:NSMakeRange(begin, end - begin)]; + NSMutableArray *playList = [NSMutableArray array]; + for (int i = 0; i < preLines2.count; i++) { + NSString *path = preLines2[i]; + if (!path || [path length] == 0) { + continue; + } + if ([path hasPrefix:@"#"]) { + continue; + } + if ([path hasPrefix:@"--break"]) { + break; + } + NSURL *url = [NSURL URLWithString:path]; + [playList addObject:url]; + } + NSLog(@"从XList读取到:%lu个视频文件",(unsigned long)playList.count); + return [playList copy]; +} + - (void)appendToPlayList:(NSArray *)bookmarkArr reset:(BOOL)reset { NSMutableArray *videos = [NSMutableArray array]; @@ -1012,6 +1043,14 @@ - (void)appendToPlayList:(NSArray *)bookmarkArr reset:(BOOL)reset if ([self existTaskForUrl:url]) { continue; } + if ([[[url pathExtension] lowercaseString] isEqualToString:@"xlist"]) { + if (reset) { + [self.playList removeAllObjects]; + } + [self.playList addObjectsFromArray:[[self class] parseXPlayList:url]]; + [self playFirstIfNeed]; + continue; + } if ([dic[@"type"] intValue] == 0) { [videos addObject:url]; } else if ([dic[@"type"] intValue] == 1) { @@ -1077,11 +1116,19 @@ - (NSDragOperation)acceptDragOperation:(NSArray *)list BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDirectory]; if (isExist) { if (isDirectory) { + //扫描文件夹 +// NSString *dir = [url path]; +// NSArray *dicArr = [MRUtil scanFolderWithPath:dir filter:[MRUtil acceptMediaType]]; +// if ([dicArr count] > 0) { +// return NSDragOperationCopy; +// } return NSDragOperationCopy; } else { NSString *pathExtension = [[url pathExtension] lowercaseString]; if ([@"xlist" isEqualToString:pathExtension]) { return NSDragOperationCopy; + } else if ([[MRUtil acceptMediaType] containsObject:pathExtension]) { + return NSDragOperationCopy; } } } @@ -1135,7 +1182,7 @@ - (IBAction)onMoreFunc:(id)sender - (BOOL)preferHW { - return [MRUtil boolForKey:@"values.hw"]; + return [MRCocoaBindingUserDefault use_hw]; } - (void)retry @@ -1216,7 +1263,6 @@ - (IBAction)playNext:(NSButton *)sender if (idx == self.playList.count - 1) { [self doStopPlay]; [self.playList removeAllObjects]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:lastPlayedKey]; return; } @@ -1296,22 +1342,16 @@ - (IBAction)onChangeSubtitleColor:(NSPopUpButton *)sender { NSMenuItem *item = [sender selectedItem]; int bgrValue = (int)item.tag; - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; + IJKSDLSubtitlePreference p = self.player.subtitlePreference; p.color = bgrValue; - self.player.view.subtitlePreference = p; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } + self.player.subtitlePreference = p; } - (IBAction)onChangeSubtitleSize:(NSStepper *)sender { - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; - p.ratio = sender.floatValue; - self.player.view.subtitlePreference = p; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } + IJKSDLSubtitlePreference p = self.player.subtitlePreference; + p.scale = sender.floatValue / 50; + self.player.subtitlePreference = p; } - (IBAction)onSelectSubtitle:(NSPopUpButton*)sender @@ -1335,12 +1375,9 @@ - (IBAction)onChangeSubtitleDelay:(NSStepper *)sender - (IBAction)onChangeSubtitleBottomMargin:(NSSlider *)sender { - IJKSDLSubtitlePreference p = self.player.view.subtitlePreference; + IJKSDLSubtitlePreference p = self.player.subtitlePreference; p.bottomMargin = sender.floatValue; - self.player.view.subtitlePreference = p; - if (!self.player.isPlaying) { - [self.player.view setNeedsRefreshCurrentPic]; - } + self.player.subtitlePreference = p; } #pragma mark 画面设置 diff --git a/examples/macos/IJKMediaDemo/MRStatisticalViewController.xib b/examples/macos/IJKMediaDemo/MRStatisticalViewController.xib index 357e832acf..aa27eaba8f 100644 --- a/examples/macos/IJKMediaDemo/MRStatisticalViewController.xib +++ b/examples/macos/IJKMediaDemo/MRStatisticalViewController.xib @@ -44,7 +44,7 @@ - + @@ -242,7 +242,7 @@ - + @@ -685,20 +685,18 @@ - + - - - + + - - - - + + - - - - + + - - - + + - - - - + + + + + + + + + + + + + + - + @@ -767,7 +780,7 @@ - + @@ -811,12 +824,11 @@ - - - + + - + @@ -824,7 +836,7 @@ - + @@ -859,10 +871,10 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/macos/IJKMediaDemo/Settings/GeneralSettingViewController.h b/examples/macos/IJKMediaDemo/Settings/GeneralSettingViewController.h deleted file mode 100644 index bfef8ac54b..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/GeneralSettingViewController.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// GeneralSettingViewController.h -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface GeneralSettingViewController : NSViewController - -@end - -NS_ASSUME_NONNULL_END diff --git a/examples/macos/IJKMediaDemo/Settings/GeneralSettingViewController.m b/examples/macos/IJKMediaDemo/Settings/GeneralSettingViewController.m deleted file mode 100644 index a12aebd8a9..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/GeneralSettingViewController.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// GeneralSettingViewController.m -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import "GeneralSettingViewController.h" - -@interface GeneralSettingViewController () - -@end - -@implementation GeneralSettingViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - // Do view setup here. -} - -@end diff --git a/examples/macos/IJKMediaDemo/Settings/GeneralSettingViewController.xib b/examples/macos/IJKMediaDemo/Settings/GeneralSettingViewController.xib deleted file mode 100644 index 01abfb712e..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/GeneralSettingViewController.xib +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/macos/IJKMediaDemo/Settings/GraphicSettingViewController.h b/examples/macos/IJKMediaDemo/Settings/GraphicSettingViewController.h deleted file mode 100644 index 459ecbb5d8..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/GraphicSettingViewController.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// GraphicSettingViewController.h -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface GraphicSettingViewController : NSViewController - -@end - -NS_ASSUME_NONNULL_END diff --git a/examples/macos/IJKMediaDemo/Settings/GraphicSettingViewController.m b/examples/macos/IJKMediaDemo/Settings/GraphicSettingViewController.m deleted file mode 100644 index b819d82050..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/GraphicSettingViewController.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// GraphicSettingViewController.m -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import "GraphicSettingViewController.h" - -@interface GraphicSettingViewController () - -@end - -@implementation GraphicSettingViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - // Do view setup here. -} - -@end diff --git a/examples/macos/IJKMediaDemo/Settings/GraphicSettingViewController.xib b/examples/macos/IJKMediaDemo/Settings/GraphicSettingViewController.xib deleted file mode 100644 index 4117b44606..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/GraphicSettingViewController.xib +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/macos/IJKMediaDemo/Settings/LeftCategoryController.h b/examples/macos/IJKMediaDemo/Settings/LeftCategoryController.h deleted file mode 100644 index a6c176c23a..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/LeftCategoryController.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// LeftCategoryController.h -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface LeftCategoryController : NSViewController - -- (void)updateDataSource:(NSArray *)arr; -- (void)onSelectItem:(void(^)(NSDictionary *))handler; - -@end - -NS_ASSUME_NONNULL_END diff --git a/examples/macos/IJKMediaDemo/Settings/LeftCategoryController.m b/examples/macos/IJKMediaDemo/Settings/LeftCategoryController.m deleted file mode 100644 index 9d470cd9f0..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/LeftCategoryController.m +++ /dev/null @@ -1,71 +0,0 @@ -// -// LeftCategoryController.m -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import "LeftCategoryController.h" - -@interface LeftCategoryController () - -@property (nonatomic, strong) NSArray *dataArr; -@property (weak) IBOutlet NSTableView *tableView; -@property (nonatomic, copy) void (^selectHandler)(NSDictionary *); - -@end - -@implementation LeftCategoryController - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didSelectRow:) name:NSTableViewSelectionDidChangeNotification object:self.tableView]; -} - -- (void)viewWillAppear -{ - [super viewWillAppear]; - self.view.window.titlebarAppearsTransparent = YES; - self.view.window.styleMask |= NSWindowStyleMaskMiniaturizable; - self.view.window.styleMask |= NSWindowStyleMaskFullSizeContentView; - self.view.window.title = @""; -} - -- (void)updateDataSource:(NSArray *)arr -{ - self.dataArr = arr; - [self.tableView reloadData]; -} - -- (void)onSelectItem:(void (^)(NSDictionary * _Nonnull))handler -{ - self.selectHandler = handler; -} - -- (void)didSelectRow:(id)sender -{ - NSInteger row = [self.tableView selectedRow]; - NSDictionary *dic = self.dataArr[row]; - if (self.selectHandler) { - self.selectHandler(dic); - } -} - -- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView -{ - return [self.dataArr count]; -} - -- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row -{ - return self.dataArr[row]; -} - -@end diff --git a/examples/macos/IJKMediaDemo/Settings/Setting.storyboard b/examples/macos/IJKMediaDemo/Settings/Setting.storyboard deleted file mode 100644 index d3380a1f3a..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/Setting.storyboard +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/macos/IJKMediaDemo/Settings/SettingSplitViewController.h b/examples/macos/IJKMediaDemo/Settings/SettingSplitViewController.h deleted file mode 100644 index 7ec9ee0508..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/SettingSplitViewController.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// SettingSplitViewController.h -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface SettingSplitViewController : NSSplitViewController - -@end - -NS_ASSUME_NONNULL_END diff --git a/examples/macos/IJKMediaDemo/Settings/SettingSplitViewController.m b/examples/macos/IJKMediaDemo/Settings/SettingSplitViewController.m deleted file mode 100644 index 13b06e9dd0..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/SettingSplitViewController.m +++ /dev/null @@ -1,63 +0,0 @@ -// -// SettingSplitViewController.m -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import "SettingSplitViewController.h" -#import "LeftCategoryController.h" - -@interface SettingSplitViewController () - -@end - -@implementation SettingSplitViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - // Do view setup here. - - NSSplitViewItem *leftItem = [[self splitViewItems] firstObject]; - NSSplitViewItem *rightItem = [[self splitViewItems] lastObject]; - LeftCategoryController *lvc = (LeftCategoryController *)leftItem.viewController; - NSViewController *rvc = rightItem.viewController; - - [lvc updateDataSource:@[ - @{ - @"title" : @"通用", - @"vc" : @"GeneralSettingViewController" - },@{ - @"title" : @"字幕", - @"vc" : @"SubtitleSettingViewController" - },@{ - @"title" : @"画面", - @"vc" : @"GraphicSettingViewController" - },@{ - @"title" : @"截图", - @"vc" : @"SnapshotSettingViewController" - },@{ - @"title" : @"解码器", - @"vc" : @"DecoderSettingViewController" - } - ]]; - - [lvc onSelectItem:^(NSDictionary * _Nonnull dic) { - NSString *vcStr = dic[@"vc"]; - if (vcStr.length > 0) { - Class clazz = NSClassFromString(vcStr); - if (clazz) { - NSViewController *vc = [[clazz alloc] init]; - [[rvc childViewControllers] makeObjectsPerformSelector:@selector(removeFromParentViewController)]; - [[rvc.view subviews]makeObjectsPerformSelector:@selector(removeFromSuperview)]; - [rvc addChildViewController:vc]; - vc.view.frame = rvc.view.bounds; - vc.view.autoresizingMask = NSViewHeightSizable | NSViewWidthSizable; - [rvc.view addSubview:vc.view]; - } - } - }]; -} - -@end diff --git a/examples/macos/IJKMediaDemo/Settings/SnapshotSettingViewController.h b/examples/macos/IJKMediaDemo/Settings/SnapshotSettingViewController.h deleted file mode 100644 index 4d2036e102..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/SnapshotSettingViewController.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// SnapshotSettingViewController.h -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface SnapshotSettingViewController : NSViewController - -@end - -NS_ASSUME_NONNULL_END diff --git a/examples/macos/IJKMediaDemo/Settings/SnapshotSettingViewController.m b/examples/macos/IJKMediaDemo/Settings/SnapshotSettingViewController.m deleted file mode 100644 index a515534eda..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/SnapshotSettingViewController.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// SnapshotSettingViewController.m -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import "SnapshotSettingViewController.h" - -@interface SnapshotSettingViewController () - -@end - -@implementation SnapshotSettingViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - // Do view setup here. -} - -@end diff --git a/examples/macos/IJKMediaDemo/Settings/SnapshotSettingViewController.xib b/examples/macos/IJKMediaDemo/Settings/SnapshotSettingViewController.xib deleted file mode 100644 index 97e46e34e7..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/SnapshotSettingViewController.xib +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/macos/IJKMediaDemo/Settings/SubtitleSettingViewController.h b/examples/macos/IJKMediaDemo/Settings/SubtitleSettingViewController.h deleted file mode 100644 index afb57f5949..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/SubtitleSettingViewController.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// SubtitleSettingViewController.h -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface SubtitleSettingViewController : NSViewController - -@end - -NS_ASSUME_NONNULL_END diff --git a/examples/macos/IJKMediaDemo/Settings/SubtitleSettingViewController.m b/examples/macos/IJKMediaDemo/Settings/SubtitleSettingViewController.m deleted file mode 100644 index ca5e2c2042..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/SubtitleSettingViewController.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// SubtitleSettingViewController.m -// IJKMediaMacDemo -// -// Created by Reach Matt on 2022/2/24. -// Copyright © 2022 IJK Mac. All rights reserved. -// - -#import "SubtitleSettingViewController.h" - -@interface SubtitleSettingViewController () - -@end - -@implementation SubtitleSettingViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - // Do view setup here. -} - -@end diff --git a/examples/macos/IJKMediaDemo/Settings/SubtitleSettingViewController.xib b/examples/macos/IJKMediaDemo/Settings/SubtitleSettingViewController.xib deleted file mode 100644 index 9c9321efc5..0000000000 --- a/examples/macos/IJKMediaDemo/Settings/SubtitleSettingViewController.xib +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/macos/IJKMediaDemo/ipad8225552_4897622324404_1436873-no-dis.m3u8 b/examples/macos/IJKMediaDemo/ipad8225552_4897622324404_1436873-no-dis.m3u8 deleted file mode 100644 index 83d71e8b53..0000000000 --- a/examples/macos/IJKMediaDemo/ipad8225552_4897622324404_1436873-no-dis.m3u8 +++ /dev/null @@ -1,160 +0,0 @@ -#EXTM3U -#EXT-X-TARGETDURATION:30 -#EXT-X-VERSION:3 -#EXTINF:6.04, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2swm8ioF24lGd4r&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:11.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F24lGd4wm8ioF2sWBXOZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:10.32, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWBXOZYoAgmPcWJyHff&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:13.16, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfBXtwm8ioF24fYXtWf&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:10.84, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F24fYXtWGoAgmPcWD6Hff&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:11, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F24WhXtwm8ioF2tfJXtr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:11.12, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2tfJXtwm8ioF2tWOXtWK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:11.64, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2tWOXtWJoAgmPcRYeHfO9&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:12.68, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2XfDXOWGoAgmPcRhdHfYk&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:7.8, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2XRYXsfFoAgmPcWh14lGd4r&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:12.399, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWYeHZYbSoMXAZD6sRYXOfOO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:8.921, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWhdHfJWXvm8ioF2sWJyHfh9&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:10.92, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWJyHfhNSoMXAZD6ORYXsZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:12.08, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWOdHfYdSoMXAZD64WYXsWf&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:11.36, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWD1HfhNSoMXAZD6tfhX4WK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:14.92, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWG6HfDvSoMXAZD6tWGX4fw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:10.56, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWBNHfFbSoMXAZD6XfZ&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sRYySoMXAZD6XWZ&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:9.679, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sRhySoMXAZDvsfGXtfBO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:5.801, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfYNHfGyXvm8ioF2OfhvHfFs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfhvHfFdSoMXAZDvOfJX4Zr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfJvHfFdSoMXAZDvOWJX4Zr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:12.4, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfOvHfFdSoMXAZDv4fFXXZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:13.24, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfFbHZYdSoMXAZDv4RYXsWK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:12.48, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfDdHfhvSoMXAZDvtWYXtr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:10.08, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfB1HfGoAgmPcWJds5GNXr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:14.879, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OZY1HfGdSoMXAZDvXWDX4WDO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:4.268, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OZheHfDeXvm8ioF2OWY1HfYvtY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=0.0&bv=0.0&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB -#EXTINF:14.599, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD1SoMXAZD645GWXRY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:9.401, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD645GWXRhoAgmPcWJWHZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:14.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvOlGdSoMXAZDWX5Gv4r&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10.76, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDWX5Gv4wm8ioF24ZY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:12.839, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDbXvm8ioF2tfhXXfOO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10.001, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDNslGdORhoAgmPcWB6HZYk&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:7.88, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDyslGd4wm8ioF2tRhXtWK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:9.92, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDyXlGyOwm8ioF2XZhXtfw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDdXlGN4wm8ioF2XRhXtfw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:9.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDAXlGN4wm8ioF2sWYAHfYs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:12.159, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6sZhXsZYoAgmPcWhvslGvORY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10.001, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6OfhXOfOASoMXAZD6OWhXOfw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:11.56, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6OWhXOfFoAgmPcWhbO5Gs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD64fJXXwm8ioF2sWDvHZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD64WJXXwm8ioF2sWGvHZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:13.08, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6tfJXXwm8ioF2sWBeHZYs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:11.799, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6tWDXXZYoAgmPcWhdtlGNtRY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10.28, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6XfBXtfBASoMXAZD6XWBXXWDO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:13.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6XWBXXWDASoMXAZDvsWhXORhO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10.041, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvsWhXORhASoMXAZDvOfhX4fw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:8.319, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvOfhX4fFoAgmPcWJvXlGy4RY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvOZhXtWDASoMXAZDvORhXtWDO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:11.801, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvORhXtWDASoMXAZDv4WhX4Wf&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:9.999, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDv4WhX4WGoAgmPcWJNslGe4RY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:7.72, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvtfhX4WDASoMXAZDvtZhXOfBO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:13.081, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvtZhXOfBASoMXAZDvXfJXOWf&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:12.16, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvXfJXOWGoAgmPcWJA45GeOr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:5.292, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvXWFX4WJoAgmPcWO1s5G1sWK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=300.027&bv=300.027&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:5.04, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2swm8ioF245Gd4r&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:12.2, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F245Gd4wm8ioF2sWBXsfw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:6.76, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWBXsfFoAgmPcWJWHZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:11.84, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfOXXwm8ioF2OWDXtfw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:14.84, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OWDXtfFoAgmPcWD1HfFs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:11.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F24WYX4ZYoAgmPcWG6HZhE&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:12.88, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2tfhXXWJoAgmPcWBbHZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:12.36, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2tWFXXwm8ioF2XfBXsWf&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2XfBXsWGoAgmPcRhyHfh9&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:10.64, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2XWBXsWGoAgmPcWh1tlGs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:13.2, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWYyHZYoAgmPcWhvsY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:12.32, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWJ6SoMXAZD6OWOXOWK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:12.16, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWOWHfOvSoMXAZD64fDX4Zr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:14.96, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWFeHfFdSoMXAZD6tfYX4fw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:9.319, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWG1HfFbSoMXAZD6tZhXtWDO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWGAHfBeXvm8ioF2sWBAHfBeXY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:13.92, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWBAHfBeXvm8ioF2sRhWHfGyXY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:6.961, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sRhWHfGyXvm8ioF2OfY1HfGk&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:11.88, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfY1HfGbSoMXAZDvsWJX4WK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:9.879, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfhvHfDvSoMXAZDvOfJXORhO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:10.56, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfJvHfOAXvm8ioF2OfOvHZheXY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:11.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfOvHZheXvm8ioF2OfFbHfOAXY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB24vm8I9kIWr&ba=600.039&bv=600.039&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXT-X-ENDLIST diff --git a/examples/macos/IJKMediaDemo/ipad8225552_4897622324404_1436873.m3u8 b/examples/macos/IJKMediaDemo/ipad8225552_4897622324404_1436873.m3u8 deleted file mode 100644 index 42cda86fa0..0000000000 --- a/examples/macos/IJKMediaDemo/ipad8225552_4897622324404_1436873.m3u8 +++ /dev/null @@ -1,166 +0,0 @@ -#EXTM3U -#EXT-X-TARGETDURATION:30 -#EXT-X-VERSION:3 -#EXTINF:6.04, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2swm8ioF24lGd4r&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=1 -#EXTINF:11.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F24lGd4wm8ioF2sWBXOZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=2 -#EXTINF:10.32, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWBXOZYoAgmPcWJyHff&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=3 -#EXTINF:13.16, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfBXtwm8ioF24fYXtWf&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=4 -#EXTINF:10.84, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F24fYXtWGoAgmPcWD6Hff&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=5 -#EXTINF:11, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F24WhXtwm8ioF2tfJXtr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=6 -#EXTINF:11.12, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2tfJXtwm8ioF2tWOXtWK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=7 -#EXTINF:11.64, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2tWOXtWJoAgmPcRYeHfO9&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=8 -#EXTINF:12.68, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2XfDXOWGoAgmPcRhdHfYk&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=9 -#EXTINF:7.8, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2XRYXsfFoAgmPcWh14lGd4r&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=10 -#EXTINF:12.399, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWYeHZYbSoMXAZD6sRYXOfOO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=11 -#EXTINF:8.921, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWhdHfJWXvm8ioF2sWJyHfh9&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=12 -#EXTINF:10.92, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWJyHfhNSoMXAZD6ORYXsZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=13 -#EXTINF:12.08, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWOdHfYdSoMXAZD64WYXsWf&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=14 -#EXTINF:11.36, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWD1HfhNSoMXAZD6tfhX4WK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=15 -#EXTINF:14.92, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWG6HfDvSoMXAZD6tWGX4fw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=16 -#EXTINF:10.56, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWBNHfFbSoMXAZD6XfZ&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=17 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sRYySoMXAZD6XWZ&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=18 -#EXTINF:9.679, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sRhySoMXAZDvsfGXtfBO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=19 -#EXTINF:5.801, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfYNHfGyXvm8ioF2OfhvHfFs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=20 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfhvHfFdSoMXAZDvOfJX4Zr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=21 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfJvHfFdSoMXAZDvOWJX4Zr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=22 -#EXTINF:12.4, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfOvHfFdSoMXAZDv4fFXXZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=23 -#EXTINF:13.24, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfFbHZYdSoMXAZDv4RYXsWK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=24 -#EXTINF:12.48, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfDdHfhvSoMXAZDvtWYXtr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=25 -#EXTINF:10.08, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfB1HfGoAgmPcWJds5GNXr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=26 -#EXTINF:14.879, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OZY1HfGdSoMXAZDvXWDX4WDO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=27 -#EXTINF:4.268, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfOcsRY1HDK9t0Lf7PSWDWEytTS93yAfXb8vePhXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OZheHfDeXvm8ioF2OWY1HfYvtY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=9Ft5kTIOf_vtzB2psx911lEci7p9t7YB&i=28 -#EXT-X-DISCONTINUITY -#EXTINF:14.599, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD1SoMXAZD645GWXRY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:9.401, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD645GWXRhoAgmPcWJWHZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:14.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvOlGdSoMXAZDWX5Gv4r&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10.76, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDWX5Gv4wm8ioF24ZY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:12.839, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDbXvm8ioF2tfhXXfOO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10.001, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDNslGdORhoAgmPcWB6HZYk&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:7.88, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDyslGd4wm8ioF2tRhXtWK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:9.92, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDyXlGyOwm8ioF2XZhXtfw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDdXlGN4wm8ioF2XRhXtfw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:9.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDAXlGN4wm8ioF2sWYAHfYs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:12.159, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6sZhXsZYoAgmPcWhvslGvORY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10.001, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6OfhXOfOASoMXAZD6OWhXOfw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:11.56, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6OWhXOfFoAgmPcWhbO5Gs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD64fJXXwm8ioF2sWDvHZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD64WJXXwm8ioF2sWGvHZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:13.08, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6tfJXXwm8ioF2sWBeHZYs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:11.799, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6tWDXXZYoAgmPcWhdtlGNtRY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10.28, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6XfBXtfBASoMXAZD6XWBXXWDO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:13.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZD6XWBXXWDASoMXAZDvsWhXORhO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10.041, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvsWhXORhASoMXAZDvOfhX4fw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:8.319, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvOfhX4fFoAgmPcWJvXlGy4RY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvOZhXtWDASoMXAZDvORhXtWDO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:11.801, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvORhXtWDASoMXAZDv4WhX4Wf&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:9.999, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDv4WhX4WGoAgmPcWJNslGe4RY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:7.72, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvtfhX4WDASoMXAZDvtZhXOfBO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:13.081, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvtZhXOfBASoMXAZDvXfJXOWf&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:12.16, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvXfJXOWGoAgmPcWJA45GeOr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXTINF:5.292, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB64fFc4ZYcMyp9WMTN6h2b3hVWm0AEUZpw1DYdllm47fFo70F2svmfVZDvHfhWS0TPcRY1sfho7qLK2ZD64wm1BqVPcgToiuYoGNh2OwmW2oTv2ZDvXWFX4WJoAgmPcWO1s5G1sWK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=so49AzEjxGUWgDfTwgEVAkwuCPax7tX7 -#EXT-X-DISCONTINUITY -#EXTINF:5.04, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2swm8ioF245Gd4r&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:12.2, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F245Gd4wm8ioF2sWBXsfw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:6.76, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWBXsfFoAgmPcWJWHZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:11.84, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfOXXwm8ioF2OWDXtfw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:14.84, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OWDXtfFoAgmPcWD1HfFs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:11.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F24WYX4ZYoAgmPcWG6HZhE&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:12.88, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2tfhXXWJoAgmPcWBbHZr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:12.36, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2tWFXXwm8ioF2XfBXsWf&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2XfBXsWGoAgmPcRhyHfh9&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:10.64, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2XWBXsWGoAgmPcWh1tlGs&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:13.2, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWYyHZYoAgmPcWhvsY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:12.32, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWJ6SoMXAZD6OWOXOWK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:12.16, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWOWHfOvSoMXAZD64fDX4Zr&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:14.96, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWFeHfFdSoMXAZD6tfYX4fw&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:9.319, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWG1HfFbSoMXAZD6tZhXtWDO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:10, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWGAHfBeXvm8ioF2sWBAHfBeXY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:13.92, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sWBAHfBeXvm8ioF2sRhWHfGyXY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:6.961, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2sRhWHfGyXvm8ioF2OfY1HfGk&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:11.88, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfY1HfGbSoMXAZDvsWJX4WK&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:9.879, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfhvHfDvSoMXAZDvOfJXORhO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:10.56, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfJvHfOAXvm8ioF2OfOvHZheXY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:11.44, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfOvHZheXvm8ioF2OfFbHfOAXY&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:10.161, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfFbHfOAXvm8ioF2OfDbHfD9&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXTINF:14.999, -https://data.vod.itc.cn/m3u8?k=Xilmz9FujZOAXAOdXm3hJvINwmN7ZD6S0mbcymvBvmoCgL8clB6sfBcOfJAHDE4mNOdUNtKlqtPDq8c7zpv1g6dMhYXUyYbS0pbcWhoGyG2O5G6Ovm6AZDdsfY6S0psdyF2sWFo70ScAZMEmqVdSotKcWJoByLKB0F2OfDbHfDNSoMXAZDvtZhX4WDO&a=j9lvzSk30pxBoL1Uq6OL4p3Cj6OLsHJlxUIAoD2sWY6sWDbjWh1sWhetZO6sfh64fFoVqTPcRYvOfDe4WJo7oB2svm8I9kIWr&sig=gN1N_vwMgJ7hG3sxVxwJtJUK2JrE5AZM -#EXT-X-ENDLIST diff --git a/examples/macos/IJKMediaMacDemo.xcodeproj/project.pbxproj b/examples/macos/IJKMediaMacDemo.xcodeproj/project.pbxproj index be023dd904..88e36d8099 100644 --- a/examples/macos/IJKMediaMacDemo.xcodeproj/project.pbxproj +++ b/examples/macos/IJKMediaMacDemo.xcodeproj/project.pbxproj @@ -14,28 +14,9 @@ C905CF1D2B550C7400D59730 /* MRStatisticalViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C905CF1B2B550C7400D59730 /* MRStatisticalViewController.xib */; }; C905CF212B55163C00D59730 /* MRRootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C905CF1F2B55163C00D59730 /* MRRootViewController.m */; }; C905CF222B55163C00D59730 /* MRRootViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C905CF202B55163C00D59730 /* MRRootViewController.xib */; }; - C955003E27C75A580026AB17 /* Setting.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C955003D27C75A580026AB17 /* Setting.storyboard */; }; - C955004227C75E740026AB17 /* LeftCategoryController.m in Sources */ = {isa = PBXBuildFile; fileRef = C955004127C75E740026AB17 /* LeftCategoryController.m */; }; - C955004527C7634F0026AB17 /* SettingSplitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C955004427C7634F0026AB17 /* SettingSplitViewController.m */; }; - C955004A27C768DD0026AB17 /* SubtitleSettingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C955004827C768DD0026AB17 /* SubtitleSettingViewController.m */; }; - C955004B27C768DD0026AB17 /* SubtitleSettingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C955004927C768DD0026AB17 /* SubtitleSettingViewController.xib */; }; - C955004F27C769050026AB17 /* GeneralSettingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C955004D27C769050026AB17 /* GeneralSettingViewController.m */; }; - C955005027C769050026AB17 /* GeneralSettingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C955004E27C769050026AB17 /* GeneralSettingViewController.xib */; }; - C955005427C769770026AB17 /* SnapshotSettingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C955005227C769770026AB17 /* SnapshotSettingViewController.m */; }; - C955005527C769770026AB17 /* SnapshotSettingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C955005327C769770026AB17 /* SnapshotSettingViewController.xib */; }; - C955005927C7698F0026AB17 /* DecoderSettingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C955005727C7698F0026AB17 /* DecoderSettingViewController.m */; }; - C955005A27C7698F0026AB17 /* DecoderSettingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C955005827C7698F0026AB17 /* DecoderSettingViewController.xib */; }; - C955005E27C769D30026AB17 /* GraphicSettingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C955005C27C769D30026AB17 /* GraphicSettingViewController.m */; }; - C955005F27C769D30026AB17 /* GraphicSettingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C955005D27C769D30026AB17 /* GraphicSettingViewController.xib */; }; - C961424D2A33253100335B5B /* ipad8225552_4897622324404_1436873.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = C961424C2A33253100335B5B /* ipad8225552_4897622324404_1436873.m3u8 */; }; - C969BBAF27BBA4E1008BC2AD /* 5003509-693880-2.webvtt in Resources */ = {isa = PBXBuildFile; fileRef = C969BBAB27BBA4E1008BC2AD /* 5003509-693880-2.webvtt */; }; - C969BBB027BBA4E1008BC2AD /* 5003509-693880-5.webvtt in Resources */ = {isa = PBXBuildFile; fileRef = C969BBAC27BBA4E1008BC2AD /* 5003509-693880-5.webvtt */; }; - C969BBB227BBA4E1008BC2AD /* 5003509-693880-3.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = C969BBAE27BBA4E1008BC2AD /* 5003509-693880-3.m3u8 */; }; C969BE4B27BDCD27008BC2AD /* MRGlobalNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = C969BE4927BDCD27008BC2AD /* MRGlobalNotification.m */; }; C969BE4E27BDD34E008BC2AD /* MRUtil+SystemPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = C969BE4C27BDD34E008BC2AD /* MRUtil+SystemPanel.m */; }; - C969BEBA27BDEAD0008BC2AD /* 5003509-693880-1.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = C969BEB927BDEAD0008BC2AD /* 5003509-693880-1.m3u8 */; }; C96F5B9B291A052100C79E07 /* MRProgressIndicator.m in Sources */ = {isa = PBXBuildFile; fileRef = C96F5B99291A052000C79E07 /* MRProgressIndicator.m */; }; - C96F5B9D291A4B1100C79E07 /* 2e0fb226-d7c3-4672-a4bc.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = C96F5B9C291A4B1100C79E07 /* 2e0fb226-d7c3-4672-a4bc.m3u8 */; }; C97423CE2A3AE8BE001F6B54 /* NSString+Ex.m in Sources */ = {isa = PBXBuildFile; fileRef = C97423CD2A3AE8BE001F6B54 /* NSString+Ex.m */; }; C97674EE2A1F333D00B7627E /* MRTextInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C97674EC2A1F333D00B7627E /* MRTextInfoViewController.m */; }; C97674EF2A1F333D00B7627E /* MRTextInfoViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C97674ED2A1F333D00B7627E /* MRTextInfoViewController.xib */; }; @@ -43,7 +24,12 @@ C99B0F1A29DE76B100E88ECB /* MRRenderViewAuxProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = C99B0F1929DE76B100E88ECB /* MRRenderViewAuxProxy.m */; }; C99B0F9229DE9D2500E88ECB /* MultiRenderSample.m in Sources */ = {isa = PBXBuildFile; fileRef = C99B0F9029DE9D2500E88ECB /* MultiRenderSample.m */; }; C99B0F9329DE9D2500E88ECB /* MultiRenderSample.xib in Resources */ = {isa = PBXBuildFile; fileRef = C99B0F9129DE9D2500E88ECB /* MultiRenderSample.xib */; }; - C9A347DF2A370AD9004AF185 /* ipad8225552_4897622324404_1436873-no-dis.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = C9A347DE2A370AD9004AF185 /* ipad8225552_4897622324404_1436873-no-dis.m3u8 */; }; + C9C291D02B623A7400C42B5C /* MRCocoaBindingUserDefault.m in Sources */ = {isa = PBXBuildFile; fileRef = C9C291CD2B6234FB00C42B5C /* MRCocoaBindingUserDefault.m */; }; + C9C7E27A2B60E37200D5F2E0 /* MRPlaylistViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C9C7E2782B60E37200D5F2E0 /* MRPlaylistViewController.m */; }; + C9C7E27B2B60E37200D5F2E0 /* MRPlaylistViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9C7E2792B60E37200D5F2E0 /* MRPlaylistViewController.xib */; }; + C9C7E27E2B60E54A00D5F2E0 /* MRPlaylistRowView.m in Sources */ = {isa = PBXBuildFile; fileRef = C9C7E27D2B60E54A00D5F2E0 /* MRPlaylistRowView.m */; }; + C9C7E2822B60F09100D5F2E0 /* MRPlayerSettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C9C7E2802B60F09100D5F2E0 /* MRPlayerSettingsViewController.m */; }; + C9C7E2832B60F09100D5F2E0 /* MRPlayerSettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9C7E2812B60F09100D5F2E0 /* MRPlayerSettingsViewController.xib */; }; C9FFF21527C864C100284B46 /* MRProgressSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = C9FFF21327C864C100284B46 /* MRProgressSlider.m */; }; DD0443E627E7749800CB946B /* MRActionProcessor+Private.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0443DE27E7749800CB946B /* MRActionProcessor+Private.m */; }; DD0443E727E7749800CB946B /* MRActionProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0443E027E7749800CB946B /* MRActionProcessor.m */; }; @@ -51,7 +37,6 @@ DD0443E927E7749800CB946B /* MRActionItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0443E327E7749800CB946B /* MRActionItem.m */; }; DD050F9D25A43A1300F01E2F /* MRUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = DD050F9625A43A1300F01E2F /* MRUtil.m */; }; DD050F9E25A43A1300F01E2F /* MRDragView.m in Sources */ = {isa = PBXBuildFile; fileRef = DD050F9C25A43A1300F01E2F /* MRDragView.m */; }; - DD1B345722CDD89400022FCA /* 996747-5277368-31.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = DD1B345622CDD89400022FCA /* 996747-5277368-31.m3u8 */; }; DD4EB00127310F1C00FBCD3A /* WindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD4EB00027310F1C00FBCD3A /* WindowController.m */; }; DD9B138D22C23A79002D1D79 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DD9B138C22C23A79002D1D79 /* AppDelegate.m */; }; DD9B138F22C23A7A002D1D79 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DD9B138E22C23A7A002D1D79 /* Assets.xcassets */; }; @@ -90,39 +75,13 @@ C905CF1E2B55163C00D59730 /* MRRootViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRRootViewController.h; sourceTree = ""; }; C905CF1F2B55163C00D59730 /* MRRootViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRRootViewController.m; sourceTree = ""; }; C905CF202B55163C00D59730 /* MRRootViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MRRootViewController.xib; sourceTree = ""; }; - C955003D27C75A580026AB17 /* Setting.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Setting.storyboard; sourceTree = ""; }; - C955004027C75E740026AB17 /* LeftCategoryController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LeftCategoryController.h; sourceTree = ""; }; - C955004127C75E740026AB17 /* LeftCategoryController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LeftCategoryController.m; sourceTree = ""; }; - C955004327C7634F0026AB17 /* SettingSplitViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingSplitViewController.h; sourceTree = ""; }; - C955004427C7634F0026AB17 /* SettingSplitViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SettingSplitViewController.m; sourceTree = ""; }; C955004627C765A30026AB17 /* PrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrefixHeader.pch; sourceTree = ""; }; - C955004727C768DD0026AB17 /* SubtitleSettingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SubtitleSettingViewController.h; sourceTree = ""; }; - C955004827C768DD0026AB17 /* SubtitleSettingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SubtitleSettingViewController.m; sourceTree = ""; }; - C955004927C768DD0026AB17 /* SubtitleSettingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SubtitleSettingViewController.xib; sourceTree = ""; }; - C955004C27C769050026AB17 /* GeneralSettingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneralSettingViewController.h; sourceTree = ""; }; - C955004D27C769050026AB17 /* GeneralSettingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GeneralSettingViewController.m; sourceTree = ""; }; - C955004E27C769050026AB17 /* GeneralSettingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GeneralSettingViewController.xib; sourceTree = ""; }; - C955005127C769770026AB17 /* SnapshotSettingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SnapshotSettingViewController.h; sourceTree = ""; }; - C955005227C769770026AB17 /* SnapshotSettingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SnapshotSettingViewController.m; sourceTree = ""; }; - C955005327C769770026AB17 /* SnapshotSettingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SnapshotSettingViewController.xib; sourceTree = ""; }; - C955005627C7698F0026AB17 /* DecoderSettingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DecoderSettingViewController.h; sourceTree = ""; }; - C955005727C7698F0026AB17 /* DecoderSettingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DecoderSettingViewController.m; sourceTree = ""; }; - C955005827C7698F0026AB17 /* DecoderSettingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DecoderSettingViewController.xib; sourceTree = ""; }; - C955005B27C769D30026AB17 /* GraphicSettingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GraphicSettingViewController.h; sourceTree = ""; }; - C955005C27C769D30026AB17 /* GraphicSettingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GraphicSettingViewController.m; sourceTree = ""; }; - C955005D27C769D30026AB17 /* GraphicSettingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GraphicSettingViewController.xib; sourceTree = ""; }; - C961424C2A33253100335B5B /* ipad8225552_4897622324404_1436873.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ipad8225552_4897622324404_1436873.m3u8; sourceTree = ""; }; - C969BBAB27BBA4E1008BC2AD /* 5003509-693880-2.webvtt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "5003509-693880-2.webvtt"; sourceTree = ""; }; - C969BBAC27BBA4E1008BC2AD /* 5003509-693880-5.webvtt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "5003509-693880-5.webvtt"; sourceTree = ""; }; - C969BBAE27BBA4E1008BC2AD /* 5003509-693880-3.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "5003509-693880-3.m3u8"; sourceTree = ""; }; C969BE4927BDCD27008BC2AD /* MRGlobalNotification.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MRGlobalNotification.m; sourceTree = ""; }; C969BE4A27BDCD27008BC2AD /* MRGlobalNotification.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MRGlobalNotification.h; sourceTree = ""; }; C969BE4C27BDD34E008BC2AD /* MRUtil+SystemPanel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "MRUtil+SystemPanel.m"; sourceTree = ""; }; C969BE4D27BDD34E008BC2AD /* MRUtil+SystemPanel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MRUtil+SystemPanel.h"; sourceTree = ""; }; - C969BEB927BDEAD0008BC2AD /* 5003509-693880-1.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "5003509-693880-1.m3u8"; sourceTree = ""; }; C96F5B99291A052000C79E07 /* MRProgressIndicator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRProgressIndicator.m; sourceTree = ""; }; C96F5B9A291A052100C79E07 /* MRProgressIndicator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRProgressIndicator.h; sourceTree = ""; }; - C96F5B9C291A4B1100C79E07 /* 2e0fb226-d7c3-4672-a4bc.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "2e0fb226-d7c3-4672-a4bc.m3u8"; sourceTree = ""; }; C97423CC2A3AE8BE001F6B54 /* NSString+Ex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+Ex.h"; sourceTree = ""; }; C97423CD2A3AE8BE001F6B54 /* NSString+Ex.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+Ex.m"; sourceTree = ""; }; C97674EB2A1F333D00B7627E /* MRTextInfoViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MRTextInfoViewController.h; sourceTree = ""; }; @@ -135,7 +94,16 @@ C99B0F8F29DE9D2500E88ECB /* MultiRenderSample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MultiRenderSample.h; sourceTree = ""; }; C99B0F9029DE9D2500E88ECB /* MultiRenderSample.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MultiRenderSample.m; sourceTree = ""; }; C99B0F9129DE9D2500E88ECB /* MultiRenderSample.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MultiRenderSample.xib; sourceTree = ""; }; - C9A347DE2A370AD9004AF185 /* ipad8225552_4897622324404_1436873-no-dis.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "ipad8225552_4897622324404_1436873-no-dis.m3u8"; sourceTree = ""; }; + C9C291CC2B6234FB00C42B5C /* MRCocoaBindingUserDefault.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MRCocoaBindingUserDefault.h; sourceTree = ""; }; + C9C291CD2B6234FB00C42B5C /* MRCocoaBindingUserDefault.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MRCocoaBindingUserDefault.m; sourceTree = ""; }; + C9C7E2772B60E37200D5F2E0 /* MRPlaylistViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MRPlaylistViewController.h; sourceTree = ""; }; + C9C7E2782B60E37200D5F2E0 /* MRPlaylistViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MRPlaylistViewController.m; sourceTree = ""; }; + C9C7E2792B60E37200D5F2E0 /* MRPlaylistViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MRPlaylistViewController.xib; sourceTree = ""; }; + C9C7E27C2B60E54A00D5F2E0 /* MRPlaylistRowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRPlaylistRowView.h; sourceTree = ""; }; + C9C7E27D2B60E54A00D5F2E0 /* MRPlaylistRowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRPlaylistRowView.m; sourceTree = ""; }; + C9C7E27F2B60F09100D5F2E0 /* MRPlayerSettingsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MRPlayerSettingsViewController.h; sourceTree = ""; }; + C9C7E2802B60F09100D5F2E0 /* MRPlayerSettingsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MRPlayerSettingsViewController.m; sourceTree = ""; }; + C9C7E2812B60F09100D5F2E0 /* MRPlayerSettingsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MRPlayerSettingsViewController.xib; sourceTree = ""; }; C9FFF21327C864C100284B46 /* MRProgressSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRProgressSlider.m; sourceTree = ""; }; C9FFF21427C864C100284B46 /* MRProgressSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRProgressSlider.h; sourceTree = ""; }; DD0443DC27E7749800CB946B /* MRActionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRActionManager.h; sourceTree = ""; }; @@ -152,7 +120,6 @@ DD050F9625A43A1300F01E2F /* MRUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRUtil.m; sourceTree = ""; }; DD050F9B25A43A1300F01E2F /* MRDragView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MRDragView.h; sourceTree = ""; }; DD050F9C25A43A1300F01E2F /* MRDragView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MRDragView.m; sourceTree = ""; }; - DD1B345622CDD89400022FCA /* 996747-5277368-31.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "996747-5277368-31.m3u8"; sourceTree = ""; }; DD4EAFFF27310F1C00FBCD3A /* WindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WindowController.h; sourceTree = ""; }; DD4EB00027310F1C00FBCD3A /* WindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WindowController.m; sourceTree = ""; }; DD9B138822C23A79002D1D79 /* IJKMediaMacDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IJKMediaMacDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -216,33 +183,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - C955003F27C75DFC0026AB17 /* Settings */ = { - isa = PBXGroup; - children = ( - C955003D27C75A580026AB17 /* Setting.storyboard */, - C955004327C7634F0026AB17 /* SettingSplitViewController.h */, - C955004427C7634F0026AB17 /* SettingSplitViewController.m */, - C955004027C75E740026AB17 /* LeftCategoryController.h */, - C955004127C75E740026AB17 /* LeftCategoryController.m */, - C955004727C768DD0026AB17 /* SubtitleSettingViewController.h */, - C955004827C768DD0026AB17 /* SubtitleSettingViewController.m */, - C955004927C768DD0026AB17 /* SubtitleSettingViewController.xib */, - C955004C27C769050026AB17 /* GeneralSettingViewController.h */, - C955004D27C769050026AB17 /* GeneralSettingViewController.m */, - C955004E27C769050026AB17 /* GeneralSettingViewController.xib */, - C955005127C769770026AB17 /* SnapshotSettingViewController.h */, - C955005227C769770026AB17 /* SnapshotSettingViewController.m */, - C955005327C769770026AB17 /* SnapshotSettingViewController.xib */, - C955005627C7698F0026AB17 /* DecoderSettingViewController.h */, - C955005727C7698F0026AB17 /* DecoderSettingViewController.m */, - C955005827C7698F0026AB17 /* DecoderSettingViewController.xib */, - C955005B27C769D30026AB17 /* GraphicSettingViewController.h */, - C955005C27C769D30026AB17 /* GraphicSettingViewController.m */, - C955005D27C769D30026AB17 /* GraphicSettingViewController.xib */, - ); - path = Settings; - sourceTree = ""; - }; DD0443DB27E7749800CB946B /* MRActionKit */ = { isa = PBXGroup; children = ( @@ -307,7 +247,6 @@ DD9B138A22C23A79002D1D79 /* IJKMediaDemo */ = { isa = PBXGroup; children = ( - C955003F27C75DFC0026AB17 /* Settings */, DD9B138B22C23A79002D1D79 /* AppDelegate.h */, DD9B138C22C23A79002D1D79 /* AppDelegate.m */, DDB782792757266800382094 /* SHBaseView.h */, @@ -319,12 +258,22 @@ C905CF1E2B55163C00D59730 /* MRRootViewController.h */, C905CF1F2B55163C00D59730 /* MRRootViewController.m */, C905CF202B55163C00D59730 /* MRRootViewController.xib */, + C9C7E27F2B60F09100D5F2E0 /* MRPlayerSettingsViewController.h */, + C9C7E2802B60F09100D5F2E0 /* MRPlayerSettingsViewController.m */, + C9C7E2812B60F09100D5F2E0 /* MRPlayerSettingsViewController.xib */, C905CF162B54C8D300D59730 /* MRAutoTestViewController.h */, C905CF152B54C8D300D59730 /* MRAutoTestViewController.m */, C905CF142B54C8D300D59730 /* MRAutoTestViewController.xib */, C905CF1A2B550C7400D59730 /* MRStatisticalViewController.h */, C905CF192B550C7400D59730 /* MRStatisticalViewController.m */, C905CF1B2B550C7400D59730 /* MRStatisticalViewController.xib */, + C9C7E2772B60E37200D5F2E0 /* MRPlaylistViewController.h */, + C9C7E2782B60E37200D5F2E0 /* MRPlaylistViewController.m */, + C9C7E2792B60E37200D5F2E0 /* MRPlaylistViewController.xib */, + C9C291CC2B6234FB00C42B5C /* MRCocoaBindingUserDefault.h */, + C9C291CD2B6234FB00C42B5C /* MRCocoaBindingUserDefault.m */, + C9C7E27C2B60E54A00D5F2E0 /* MRPlaylistRowView.h */, + C9C7E27D2B60E54A00D5F2E0 /* MRPlaylistRowView.m */, C99B0F8F29DE9D2500E88ECB /* MultiRenderSample.h */, C99B0F9029DE9D2500E88ECB /* MultiRenderSample.m */, C99B0F9129DE9D2500E88ECB /* MultiRenderSample.xib */, @@ -344,14 +293,6 @@ DD9B139022C23A7A002D1D79 /* MainMenu.xib */, C955004627C765A30026AB17 /* PrefixHeader.pch */, DD9B139322C23A7A002D1D79 /* Info.plist */, - C961424C2A33253100335B5B /* ipad8225552_4897622324404_1436873.m3u8 */, - C9A347DE2A370AD9004AF185 /* ipad8225552_4897622324404_1436873-no-dis.m3u8 */, - C96F5B9C291A4B1100C79E07 /* 2e0fb226-d7c3-4672-a4bc.m3u8 */, - C969BEB927BDEAD0008BC2AD /* 5003509-693880-1.m3u8 */, - DD1B345622CDD89400022FCA /* 996747-5277368-31.m3u8 */, - C969BBAB27BBA4E1008BC2AD /* 5003509-693880-2.webvtt */, - C969BBAE27BBA4E1008BC2AD /* 5003509-693880-3.m3u8 */, - C969BBAC27BBA4E1008BC2AD /* 5003509-693880-5.webvtt */, DD9B139422C23A7A002D1D79 /* main.m */, DD9B139622C23A7A002D1D79 /* IJKMediaDemo.entitlements */, ); @@ -458,25 +399,13 @@ files = ( C905CF172B54C8D300D59730 /* MRAutoTestViewController.xib in Resources */, DD9B138F22C23A7A002D1D79 /* Assets.xcassets in Resources */, - C969BEBA27BDEAD0008BC2AD /* 5003509-693880-1.m3u8 in Resources */, - C955005A27C7698F0026AB17 /* DecoderSettingViewController.xib in Resources */, DD9B139222C23A7A002D1D79 /* MainMenu.xib in Resources */, C905CF1D2B550C7400D59730 /* MRStatisticalViewController.xib in Resources */, C97674EF2A1F333D00B7627E /* MRTextInfoViewController.xib in Resources */, - C961424D2A33253100335B5B /* ipad8225552_4897622324404_1436873.m3u8 in Resources */, - C96F5B9D291A4B1100C79E07 /* 2e0fb226-d7c3-4672-a4bc.m3u8 in Resources */, + C9C7E2832B60F09100D5F2E0 /* MRPlayerSettingsViewController.xib in Resources */, C905CF222B55163C00D59730 /* MRRootViewController.xib in Resources */, - C955005027C769050026AB17 /* GeneralSettingViewController.xib in Resources */, - C969BBAF27BBA4E1008BC2AD /* 5003509-693880-2.webvtt in Resources */, C99B0F9329DE9D2500E88ECB /* MultiRenderSample.xib in Resources */, - C969BBB027BBA4E1008BC2AD /* 5003509-693880-5.webvtt in Resources */, - C969BBB227BBA4E1008BC2AD /* 5003509-693880-3.m3u8 in Resources */, - C955004B27C768DD0026AB17 /* SubtitleSettingViewController.xib in Resources */, - C9A347DF2A370AD9004AF185 /* ipad8225552_4897622324404_1436873-no-dis.m3u8 in Resources */, - C955003E27C75A580026AB17 /* Setting.storyboard in Resources */, - C955005527C769770026AB17 /* SnapshotSettingViewController.xib in Resources */, - C955005F27C769D30026AB17 /* GraphicSettingViewController.xib in Resources */, - DD1B345722CDD89400022FCA /* 996747-5277368-31.m3u8 in Resources */, + C9C7E27B2B60E37200D5F2E0 /* MRPlaylistViewController.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -529,33 +458,30 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C9C7E2822B60F09100D5F2E0 /* MRPlayerSettingsViewController.m in Sources */, C99B0F1A29DE76B100E88ECB /* MRRenderViewAuxProxy.m in Sources */, - C955004F27C769050026AB17 /* GeneralSettingViewController.m in Sources */, C905CF182B54C8D300D59730 /* MRAutoTestViewController.m in Sources */, - C955004A27C768DD0026AB17 /* SubtitleSettingViewController.m in Sources */, DD0443E927E7749800CB946B /* MRActionItem.m in Sources */, C969BE4B27BDCD27008BC2AD /* MRGlobalNotification.m in Sources */, - C955005E27C769D30026AB17 /* GraphicSettingViewController.m in Sources */, DDB7827B2757266900382094 /* SHBaseView.m in Sources */, DD0443E727E7749800CB946B /* MRActionProcessor.m in Sources */, C97423CE2A3AE8BE001F6B54 /* NSString+Ex.m in Sources */, DD9B139522C23A7A002D1D79 /* main.m in Sources */, - C955005427C769770026AB17 /* SnapshotSettingViewController.m in Sources */, C97DCC0727C867EA0019874F /* MRBaseView.m in Sources */, DD4EB00127310F1C00FBCD3A /* WindowController.m in Sources */, C969BE4E27BDD34E008BC2AD /* MRUtil+SystemPanel.m in Sources */, C905CF1C2B550C7400D59730 /* MRStatisticalViewController.m in Sources */, DD9B138D22C23A79002D1D79 /* AppDelegate.m in Sources */, C96F5B9B291A052100C79E07 /* MRProgressIndicator.m in Sources */, + C9C291D02B623A7400C42B5C /* MRCocoaBindingUserDefault.m in Sources */, + C9C7E27E2B60E54A00D5F2E0 /* MRPlaylistRowView.m in Sources */, DDE9FC70273BD3CF00481560 /* NSFileManager+Sandbox.m in Sources */, C905CF212B55163C00D59730 /* MRRootViewController.m in Sources */, - C955004227C75E740026AB17 /* LeftCategoryController.m in Sources */, - C955004527C7634F0026AB17 /* SettingSplitViewController.m in Sources */, - C955005927C7698F0026AB17 /* DecoderSettingViewController.m in Sources */, C99B0F9229DE9D2500E88ECB /* MultiRenderSample.m in Sources */, C9FFF21527C864C100284B46 /* MRProgressSlider.m in Sources */, C97674EE2A1F333D00B7627E /* MRTextInfoViewController.m in Sources */, DD050F9D25A43A1300F01E2F /* MRUtil.m in Sources */, + C9C7E27A2B60E37200D5F2E0 /* MRPlaylistViewController.m in Sources */, DD0443E827E7749800CB946B /* MRActionManager.m in Sources */, DD0443E627E7749800CB946B /* MRActionProcessor+Private.m in Sources */, DD050F9E25A43A1300F01E2F /* MRDragView.m in Sources */, @@ -704,6 +630,7 @@ CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 96C96H28CU; + ENABLE_HARDENED_RUNTIME = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = IJKMediaDemo/PrefixHeader.pch; INFOPLIST_FILE = IJKMediaDemo/Info.plist; @@ -712,7 +639,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 0.10.2; PRODUCT_BUNDLE_IDENTIFIER = "cn.debugly.IJKMediaMacDemo.${DEVELOPMENT_TEAM}"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -733,6 +660,7 @@ CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 96C96H28CU; + ENABLE_HARDENED_RUNTIME = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = IJKMediaDemo/PrefixHeader.pch; INFOPLIST_FILE = IJKMediaDemo/Info.plist; @@ -741,7 +669,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.15; MARKETING_VERSION = 0.10.2; PRODUCT_BUNDLE_IDENTIFIER = "cn.debugly.IJKMediaMacDemo.${DEVELOPMENT_TEAM}"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/examples/macos/Podfile.lock b/examples/macos/Podfile.lock index 61d953b5c4..fa14235198 100644 --- a/examples/macos/Podfile.lock +++ b/examples/macos/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - IJKMediaPlayerKit (0.10.5) + - IJKMediaPlayerKit (0.11.0) DEPENDENCIES: - IJKMediaPlayerKit (from `../../`) @@ -9,8 +9,8 @@ EXTERNAL SOURCES: :path: "../../" SPEC CHECKSUMS: - IJKMediaPlayerKit: 65b39f55bda0e03320d1fe72ac7824c99f230468 + IJKMediaPlayerKit: b70eef8376bfc532e26524ae8c55112e5ae5821a PODFILE CHECKSUM: f950a6923955cd8172bbcde59a7c414fe1a4ac71 -COCOAPODS: 1.15.0 +COCOAPODS: 1.15.2 diff --git a/ijkmedia/ijkplayer/apple/ijkplayer_ios.m b/ijkmedia/ijkplayer/apple/ijkplayer_ios.m index 10836debed..8008a8188f 100644 --- a/ijkmedia/ijkplayer/apple/ijkplayer_ios.m +++ b/ijkmedia/ijkplayer/apple/ijkplayer_ios.m @@ -59,6 +59,7 @@ static void ijkmp_ios_set_glview_l(IjkMediaPlayer *mp, UIViewffplayer->vout); SDL_VoutIos_SetGLView(mp->ffplayer->vout, glView); + mp->ffplayer->gpu = SDL_CreateGPU_WithContext(glView.context); } void ijkmp_ios_set_glview(IjkMediaPlayer *mp, UIView* glView) diff --git a/ijkmedia/ijkplayer/ff_ass_parser.c b/ijkmedia/ijkplayer/ff_ass_parser.c deleted file mode 100644 index 73e6dd5ec3..0000000000 --- a/ijkmedia/ijkplayer/ff_ass_parser.c +++ /dev/null @@ -1,135 +0,0 @@ -// -// ff_ass_parser.c -// IJKMediaPlayerKit -// -// Created by Reach Matt on 2022/5/17. -// - -#include "ff_ass_parser.h" -#include - -static const char * remove_ass_line_header(const char *ass) -{ -/* - ffmpeg4 ass解析规则: - "Dialogue: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,,这里是自由城\\N{\\fn微软雅黑\\fs52.5\\b0\\bord0}{\\i1}This is Free City.{\\i}{\\fad(150,150)}在不断衍生的黑暗之中 相互交换了\r\n{\\i1}"; - "Dialogue: 0,0:00:00.00,0:00:00.00,Default,,0,0,0,,{\an3\fnDFKai-SB\c&HFEFDFD&\3c&H070101&\pos(260,422)}{\fad(300,150)}字幕来源 X2&CASO"; - - ffmpeg5 ass解析规则变了:16,0,DNOP-CHS,NTP,0000,0000,0000,,{\\fad(150,150)}在不断衍生的黑暗之中 相互交换了 - - */ - const char *tok = ass; -#define CHECK_TOK(sepretor) \ -do { \ - char *pos = strchr(tok, sepretor); \ - if (pos) {tok = pos + 1;} \ -} while(0) \ - - CHECK_TOK(':'); // skip event - CHECK_TOK(','); // skip layer - CHECK_TOK(','); // skip start_time - CHECK_TOK(','); // skip end_time - CHECK_TOK(','); // skip style - CHECK_TOK(','); // skip name - CHECK_TOK(','); // skip margin_l - CHECK_TOK(','); // skip margin_r - CHECK_TOK(','); // skip margin_v -// CHECK_TOK(','); // skip effect -#undef CHECK_TOK - return tok; -} - -static void replace_N_to_n(char *buffer) -{ - int len = (int)strlen(buffer); - if (len > 0) { - do { - char *found = strstr(buffer, "\\N"); - if (found) { - *(found) = '\n'; - memmove(found + 1, found + 2, strlen(found + 2)); - *(buffer+len - 1) = '\0'; - } else { - break; - } - } while(1); - } -} - -static void remove_last_rn(char *buffer) -{ - int len = (int)strlen(buffer); - if (len > 0) { - char *found = strstr(buffer, "\r\n"); - if (found) { - if (found + 2 == buffer + len) { - *(found) = '\0'; - } - } - } -} - -static void remove_last_n(char *buffer) -{ - int len = (int)strlen(buffer); - if (len > 0) { - char *found = strstr(buffer, "\n"); - if (found) { - if (found + 1 == buffer + len) { - *(found) = '\0'; - } - } - } -} - -static char * remove_ass_line_effect(const char *ass) -{ - while (ass && strlen(ass) > 2) { - //移除 { 开头并且 } 结尾的特效内容 - if (ass[0] == '{') { - char* end = strchr(ass, '}'); - if (end) { - ass = end + 1; - } else { - break; - } - } else { - break; - } - } - - char *buffer = av_malloc(strlen(ass) + 1); - bzero(buffer, strlen(ass) + 1); - memcpy(buffer, ass, strlen(ass)); - - while (1) { - char *left = strstr(buffer, "{"); - char *right = strstr(buffer, "}"); - if (left && right > left) { - if (right - buffer == strlen(buffer)) { - bzero(left, right - left); - } else { - int count = (int)strlen(buffer) - (int)(right - buffer); - memmove(left, right + 1, count); - bzero(left + count + 1, 1); - } - } else { - break; - } - } - return buffer; -} - -//need free! -char * parse_ass_subtitle(const char *ass) -{ - const char *text = remove_ass_line_header(ass); - if (text && strlen(text) > 0) { - char *buffer = remove_ass_line_effect(text); - replace_N_to_n(buffer); - remove_last_rn(buffer); - remove_last_n(buffer); - return buffer; - } - return NULL; -} diff --git a/ijkmedia/ijkplayer/ff_ass_parser.h b/ijkmedia/ijkplayer/ff_ass_parser.h deleted file mode 100644 index cc2650b7d7..0000000000 --- a/ijkmedia/ijkplayer/ff_ass_parser.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// ff_ass_parser.h -// IJKMediaPlayerKit -// -// Created by Reach Matt on 2022/5/17. -// - -#ifndef ff_ass_parser_h -#define ff_ass_parser_h - -#include - -//need free the return value! see av_free(); -char * parse_ass_subtitle(const char *ass); - -#endif /* ff_ass_parser_h */ diff --git a/ijkmedia/ijkplayer/ff_ass_renderer.c b/ijkmedia/ijkplayer/ff_ass_renderer.c new file mode 100644 index 0000000000..46c5f9e7af --- /dev/null +++ b/ijkmedia/ijkplayer/ff_ass_renderer.c @@ -0,0 +1,550 @@ +// +// ff_ass_renderer.c +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/3/5. +// + +#include "ff_ass_renderer.h" +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" +#include "libavutil/avstring.h" +#include "libavutil/imgutils.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "ff_subtitle_def.h" +#include "ijksdl/ijksdl_gpu.h" +#include "ff_subtitle_def_internal.h" +#include "ijksdl/ijksdl_mutex.h" + +typedef struct FF_ASS_Context { + const AVClass *priv_class; + ASS_Library *library; + ASS_Renderer *renderer; + ASS_Track *track; + SDL_mutex *mutex; + + char *fontsdir; + char *charenc; + char *force_style; + int original_w, original_h; + int bottom_margin; + int force_changed; + double scale; +} FF_ASS_Context; + +#define OFFSET(x) offsetof(FF_ASS_Context, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM + +const AVOption ff_ass_options[] = { + {"fontsdir", "set the directory containing the fonts to read", OFFSET(fontsdir), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, + {"charenc", "set input character encoding", OFFSET(charenc), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS}, + {"force_style", "force subtitle style", OFFSET(force_style), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS}, + {NULL}, +}; + +/* libass supports a log level ranging from 0 to 7 */ +static const int ass_libavfilter_log_level_map[] = { + [0] = AV_LOG_FATAL, /* MSGL_FATAL */ + [1] = AV_LOG_ERROR, /* MSGL_ERR */ + [2] = AV_LOG_WARNING, /* MSGL_WARN */ + [3] = AV_LOG_WARNING, /* */ + [4] = AV_LOG_INFO, /* MSGL_INFO */ + [5] = AV_LOG_INFO, /* */ + [6] = AV_LOG_VERBOSE, /* MSGL_V */ + [7] = AV_LOG_DEBUG, /* MSGL_DBG2 */ +}; + +static void ass_log(int ass_level, const char *fmt, va_list args, void *ctx) +{ + const int ass_level_clip = av_clip(ass_level, 0, + FF_ARRAY_ELEMS(ass_libavfilter_log_level_map) - 1); + int level = ass_libavfilter_log_level_map[ass_level_clip]; + //level = AV_LOG_ERROR; + const char *prefix = "[ass] "; + char *tmp = av_asprintf("%s%s", prefix, fmt); + av_vlog(ctx, level, tmp, args); +} + +/* Init libass */ + +static int init_libass(FF_ASS_Renderer *s) +{ + FF_ASS_Context *ass = s->priv_data; + if (!ass) { + return -1; + } + + ass->library = ass_library_init(); + if (!ass->library) { + av_log(ass, AV_LOG_ERROR, "Could not initialize libass.\n"); + return AVERROR(EINVAL); + } + + ass_set_message_cb(ass->library, ass_log, ass); + ass_set_fonts_dir(ass->library, ass->fontsdir); + ass_set_extract_fonts(ass->library, 1); + ass->renderer = ass_renderer_init(ass->library); + + if (!ass->renderer) { + av_log(ass, AV_LOG_ERROR, "Could not initialize libass renderer.\n"); + return AVERROR(EINVAL); + } + + ass->track = ass_new_track(ass->library); + if (!ass->track) { + av_log(s, AV_LOG_ERROR, "Could not create a libass track\n"); + return AVERROR(EINVAL); + } + ass->mutex = SDL_CreateMutex(); + ass->track->track_type = TRACK_TYPE_ASS; + return 0; +} + +static void set_video_size(FF_ASS_Renderer *s, int w, int h) +{ + FF_ASS_Context *ass = s->priv_data; + if (!ass) { + return; + } + + ass->original_w = w; + ass->original_h = h; + ass->scale = 1.0; + + ass_set_frame_size(ass->renderer, w, h); + ass_set_storage_size(ass->renderer, w, h); + //ass_set_cache_limits(ass->renderer, 3, 0); + ass_set_line_spacing( ass->renderer, 0.0); +// ass_set_pixel_aspect(ass->renderer, (double)w / h / +// ((double)ass->original_w / ass->original_h)); +} + +static void draw_ass_bgra(unsigned char *src, int src_w, int src_h, + int src_stride, unsigned char *dst, size_t dst_stride, + uint32_t color) +{ + const unsigned int sr = (color >> 24) & 0xff; + const unsigned int sg = (color >> 16) & 0xff; + const unsigned int sb = (color >> 8) & 0xff; + const unsigned int _sa = 0xff - (color & 0xff); + + #define COLOR_BLEND(_sa,_sc,_dc) ((_sc * _sa + _dc * (65025 - _sa)) >> 16 & 0xFF) + + for (int y = 0; y < src_h; y++) { + uint32_t *dstrow = (uint32_t *) dst; + for (int x = 0; x < src_w; x++) { + const uint32_t sa = _sa * src[x]; + + uint32_t dstpix = dstrow[x]; + uint32_t dstb = dstpix & 0xFF; + uint32_t dstg = (dstpix >> 8) & 0xFF; + uint32_t dstr = (dstpix >> 16) & 0xFF; + uint32_t dsta = (dstpix >> 24) & 0xFF; + + dstr = COLOR_BLEND(sa, sr, dstr); + dstg = COLOR_BLEND(sa, sg, dstg); + dstb = COLOR_BLEND(sa, sb, dstb); + dsta = COLOR_BLEND(sa, 255, dsta); + + dstrow[x] = dstb | (dstg << 8) | (dstr << 16) | (dsta << 24); + } + dst += dst_stride; + src += src_stride; + } + #undef COLOR_BLEND +} + +static void draw_single_inset(FFSubtitleBuffer *frame, ASS_Image *img, int insetx, int insety, int bottom_margin) +{ + if (img->w == 0 || img->h == 0) + return; + unsigned char *dst = frame->data; + int y = img->dst_y - insety - bottom_margin; + if (y < 0) { + y = 0; + } + dst += y * frame->rect.stride + (img->dst_x - insetx) * 4; + draw_ass_bgra(img->bitmap, img->w, img->h, img->stride, dst, frame->rect.stride, img->color); +} + +static int upload_buffer(FF_ASS_Renderer *s, double time_ms, FFSubtitleBuffer **buffer, int ignore_change) +{ + FF_ASS_Context *ass = s->priv_data; + if (!ass || !buffer) { + return -1; + } + + SDL_LockMutex(ass->mutex); + //update scale before render_frame;can't in other thread otherwise in find cache frame cause assert + ass_set_font_scale(ass->renderer, ass->scale); + int changed; + ASS_Image *imgs = ass_render_frame(ass->renderer, ass->track, time_ms, &changed); + +// long sec = time_ms/1000; +// int h = 0,m = 0; +// if (sec > 3600) { +// h = sec / 3600; +// } +// if (sec > 60) { +// m = sec % 3600 / 60; +// } +// sec %= 60; +// +// av_log(NULL, AV_LOG_INFO, "ass_render_frame:%02d:%02d:%02d,changed:%d,imgs:%d\n",h,m,sec,changed,!!imgs); + + if (!imgs) { + if (ass->force_changed) { + ass->force_changed = 0; + } + SDL_UnlockMutex(ass->mutex); + return -1; + } + + if (!ignore_change && changed == 0 && !ass->force_changed) { + SDL_UnlockMutex(ass->mutex); + return 0; + } + + int bm = ass->bottom_margin; + int water_mark = ass->original_h * SUBTITLE_MOVE_WATERMARK; + SDL_Rectangle dirtyRect = {0}; + + { + ASS_Image *img = imgs; + while (img) { + int y = img->dst_y; + if (y > water_mark) { + y -= bm; + if (y < 0) { + y = 0; + } else if (y + img->h > ass->original_h) { + y = ass->original_h - img->h; + } + } + SDL_Rectangle t = {img->dst_x, y, img->w, img->h}; + dirtyRect = SDL_union_rectangle(dirtyRect, t); + img = img->next; + } + } + + FFSubtitleBuffer* frame = ff_subtitle_buffer_alloc_rgba32(dirtyRect); + dirtyRect.stride = frame->rect.stride; + + int cnt = 0; + while (imgs) { + ++cnt; + int y = imgs->dst_y; + if (y > water_mark) { + y -= bm; + if (y < 0) { + y = 0; + } else if (y + imgs->h > ass->original_h) { + y = ass->original_h - imgs->h; + } + } + int offset = imgs->dst_y - y; + draw_single_inset(frame, imgs, dirtyRect.x, dirtyRect.y, offset); + imgs = imgs->next; + } + *buffer = frame; + ass->force_changed = 0; + SDL_UnlockMutex(ass->mutex); + return 1; +} + +static const char * const font_mimetypes[] = { + "font/ttf", + "font/otf", + "font/sfnt", + "font/woff", + "font/woff2", + "application/font-sfnt", + "application/font-woff", + "application/x-truetype-font", + "application/vnd.ms-opentype", + "application/x-font-ttf", + NULL +}; + +static int attachment_is_font(AVStream * st) +{ + const AVDictionaryEntry *tag = av_dict_get(st->metadata, "mimetype", NULL, AV_DICT_MATCH_CASE); + if (tag) { + for (int n = 0; font_mimetypes[n]; n++) { + if (av_strcasecmp(font_mimetypes[n], tag->value) == 0) + return 1; + } + } + return 0; +} + +static void set_attach_font(FF_ASS_Renderer *s, AVStream *st) +{ + FF_ASS_Context *ass = s->priv_data; + if (!ass) { + return; + } + + /* Load attached fonts */ + if (st->codecpar->codec_type == AVMEDIA_TYPE_ATTACHMENT && + attachment_is_font(st)) { + const AVDictionaryEntry *tag = av_dict_get(st->metadata, "filename", NULL, AV_DICT_MATCH_CASE); + if (tag) { + av_log(s, AV_LOG_DEBUG, "Loading attached font: %s\n", + tag->value); + ass_add_font(ass->library, tag->value, + (char *)st->codecpar->extradata, + st->codecpar->extradata_size); + } else { + av_log(s, AV_LOG_WARNING, + "Font attachment has no filename, ignored.\n"); + } + } +} + +static int set_subtitle_header(FF_ASS_Renderer *s, uint8_t *subtitle_header, int subtitle_header_size) +{ + FF_ASS_Context *ass = s->priv_data; + if (!ass) { + return AVERROR(EINVAL); + } + + //"sans-serif" + ass_set_fonts(ass->renderer, NULL, "Helvetica Neue", ASS_FONTPROVIDER_AUTODETECT, NULL, 1); + /* Anything else than NONE will break smooth img updating. + TODO: List and force ASS_HINTING_LIGHT for known problematic fonts */ + ass_set_hinting( ass->renderer, ASS_HINTING_NONE ); + + int ret = 0; + //ass->force_style = "MarginV=50"; + if (ass->force_style) { + char **list = NULL; + char *temp = NULL; + char *ptr = av_strtok(ass->force_style, ",", &temp); + int i = 0; + while (ptr) { + av_dynarray_add(&list, &i, ptr); + if (!list) { + ret = AVERROR(ENOMEM); + goto end; + } + ptr = av_strtok(NULL, ",", &temp); + } + av_dynarray_add(&list, &i, NULL); + if (!list) { + ret = AVERROR(ENOMEM); + goto end; + } + ass_set_style_overrides(ass->library, list); + av_free(list); + } + /* Decode subtitles and push them into the renderer (libass) */ + if (subtitle_header && subtitle_header_size > 0) + ass_process_codec_private(ass->track, + (char *)subtitle_header, + subtitle_header_size); +end: + + return ret; +} + +static void process_chunk(FF_ASS_Renderer *s, char *ass_line, long long start_time, long long duration) +{ + FF_ASS_Context *ass = s->priv_data; + if (!ass) { + return; + } + // long sec = start_time/1000; + // int h = 0,m = 0; + // if (sec > 3600) { + // h = sec / 3600; + // } + // if (sec > 60) { + // m = sec % 3600 / 60; + // } + // sec %= 60; + // + // av_log(NULL, AV_LOG_INFO, "ass_process_chunk:%02d:%02d:%02d\n",h,m,sec); + SDL_LockMutex(ass->mutex); + ass_process_chunk(ass->track, ass_line, (int)strlen(ass_line), start_time, duration); + SDL_UnlockMutex(ass->mutex); +} + +static void flush_events(FF_ASS_Renderer *s) +{ + FF_ASS_Context *ass = s->priv_data; + if (!ass) { + return; + } + SDL_LockMutex(ass->mutex); + ass_flush_events(ass->track); + SDL_UnlockMutex(ass->mutex); +} + +static void update_bottom_margin(FF_ASS_Renderer *s, int b) +{ + FF_ASS_Context *ass = s->priv_data; + if (!ass || !ass->renderer) { + return; + } + //设置后字体会被压缩变形 + //ass_set_margins(ass->renderer, 0, b, 0, 0); + SDL_LockMutex(ass->mutex); + ass->bottom_margin = b; + ass->force_changed = 1; + SDL_UnlockMutex(ass->mutex); +} + +static void set_font_scale(FF_ASS_Renderer *s, double scale) +{ + FF_ASS_Context *ass = s->priv_data; + if (!ass || !ass->renderer) { + return; + } + SDL_LockMutex(ass->mutex); + ass->scale = scale; + SDL_UnlockMutex(ass->mutex); +} + +static void uninit(FF_ASS_Renderer *s) +{ + FF_ASS_Context *ass = s->priv_data; + if (!ass) { + return; + } + SDL_LockMutex(ass->mutex); + if (ass->track) + ass_free_track(ass->track); + if (ass->renderer) + ass_renderer_done(ass->renderer); + if (ass->library) + ass_library_done(ass->library); + SDL_UnlockMutex(ass->mutex); + + SDL_DestroyMutex(ass->mutex); +} + +static void *ass_context_child_next(void *obj, void *prev) +{ + return NULL; +} + +static const AVClass subtitles_class = { + .class_name = "ff_ass_subtitles", + .item_name = av_default_item_name, + .option = ff_ass_options, + .version = LIBAVUTIL_VERSION_INT, + .child_next = ass_context_child_next, +}; + +FF_ASS_Renderer_Format ff_ass_default_format = { + .priv_class = &subtitles_class, + .priv_data_size = sizeof(FF_ASS_Context), + .init = init_libass, + .set_subtitle_header= set_subtitle_header, + .set_attach_font = set_attach_font, + .set_video_size = set_video_size, + .process_chunk = process_chunk, + .flush_events = flush_events, + .upload_buffer = upload_buffer, + .update_bottom_margin= update_bottom_margin, + .set_font_scale = set_font_scale, + .uninit = uninit, +}; + +static FF_ASS_Renderer *ffAss_create_with_format(FF_ASS_Renderer_Format* format, AVDictionary *opts) +{ + FF_ASS_Renderer *r = av_mallocz(sizeof(FF_ASS_Renderer)); + r->priv_data = av_mallocz(format->priv_data_size); + if (!r->priv_data) { + av_log(NULL, AV_LOG_ERROR, "ffAss_create:AVERROR(ENOMEM)"); + return NULL; + } + r->iformat = format; + if (format->priv_class) { + //非FF_ASS_Context的首地址,也就是第一个变量的地址赋值 + *(const AVClass**)r->priv_data = format->priv_class; + av_opt_set_defaults(r->priv_data); + } + if (opts) { + av_opt_set_dict(r->priv_data, &opts); + } + + if (format->init(r)) { + ff_ass_render_release(&r); + return NULL; + } + r->refCount = 1; + return r; +} + +FF_ASS_Renderer *ff_ass_render_create_default(uint8_t *subtitle_header, int subtitle_header_size, int video_w, int video_h, AVDictionary *opts) +{ + FF_ASS_Renderer_Format* format = &ff_ass_default_format; + FF_ASS_Renderer* r = ffAss_create_with_format(format, opts); + if (r) { + r->iformat->set_subtitle_header(r, subtitle_header, subtitle_header_size); + r->iformat->set_video_size(r, video_w, video_h); + } + return r; +} + +static void _destroy(FF_ASS_Renderer **sp) +{ + if (!sp) return; + FF_ASS_Renderer *s = *sp; + + if (s->iformat->uninit) { + s->iformat->uninit(s); + } + + if (s->iformat->priv_data_size) + av_opt_free(s->priv_data); + av_freep(&s->priv_data); + av_freep(sp); +} + +FF_ASS_Renderer * ff_ass_render_retain(FF_ASS_Renderer *ar) +{ + if (ar) { + __atomic_add_fetch(&ar->refCount, 1, __ATOMIC_RELEASE); + } + return ar; +} + +void ff_ass_render_release(FF_ASS_Renderer **arp) +{ + if (arp) { + FF_ASS_Renderer *sb = *arp; + if (sb) { + if (__atomic_add_fetch(&sb->refCount, -1, __ATOMIC_RELEASE) == 0) { + _destroy(arp); + } + } + } +} + +int ff_ass_upload_buffer(FF_ASS_Renderer * assRenderer, float begin, FFSubtitleBuffer ** buffer, int ignore_change) +{ + if (!assRenderer) { + return -1; + } + return assRenderer->iformat->upload_buffer(assRenderer, begin * 1000, buffer, ignore_change); +} + +void ff_ass_process_chunk(FF_ASS_Renderer * assRenderer, const char *ass_line, float begin, float end) +{ + if (!assRenderer) { + return; + } + assRenderer->iformat->process_chunk(assRenderer, (char *)ass_line, begin, end); +} + +void ff_ass_flush_events(FF_ASS_Renderer * assRenderer) +{ + if (!assRenderer) { + return; + } + assRenderer->iformat->flush_events(assRenderer); +} diff --git a/ijkmedia/ijkplayer/ff_ass_renderer.h b/ijkmedia/ijkplayer/ff_ass_renderer.h new file mode 100644 index 0000000000..4bb6194b7e --- /dev/null +++ b/ijkmedia/ijkplayer/ff_ass_renderer.h @@ -0,0 +1,49 @@ +// +// ff_ass_renderer.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/3/5. +// + +#ifndef ff_ass_steam_renderer_h +#define ff_ass_steam_renderer_h + +#include +#include +#include + +typedef struct FF_ASS_Renderer FF_ASS_Renderer; +typedef struct AVStream AVStream; +typedef struct FFSubtitleBuffer FFSubtitleBuffer; + +typedef struct FF_ASS_Renderer_Format { + const AVClass *priv_class; + int priv_data_size; + int (*init)(FF_ASS_Renderer *); + int (*set_subtitle_header)(FF_ASS_Renderer *s, uint8_t *subtitle_header, int subtitle_header_size); + void (*set_attach_font)(FF_ASS_Renderer *s, AVStream *st); + void (*set_video_size)(FF_ASS_Renderer *s, int w, int h); + void (*process_chunk)(FF_ASS_Renderer *s, char *ass_line, int64_t start, int64_t duration); + void (*flush_events)(FF_ASS_Renderer *s); + int (*upload_buffer)(FF_ASS_Renderer *, double time_ms, FFSubtitleBuffer **buffer, int ignore_change); + void (*update_bottom_margin)(FF_ASS_Renderer *s, int b); + void (*set_font_scale)(FF_ASS_Renderer *, double scale); + void (*uninit)(FF_ASS_Renderer *); +} FF_ASS_Renderer_Format; + +typedef struct FF_ASS_Renderer { + void *priv_data; + const FF_ASS_Renderer_Format *iformat; + int refCount; +} FF_ASS_Renderer; + +FF_ASS_Renderer *ff_ass_render_create_default(uint8_t *subtitle_header, int subtitle_header_size, int video_w, int video_h, AVDictionary *opts); + +FF_ASS_Renderer * ff_ass_render_retain(FF_ASS_Renderer *ar); +void ff_ass_render_release(FF_ASS_Renderer **arp); + +int ff_ass_upload_buffer(FF_ASS_Renderer * assRenderer, float begin, FFSubtitleBuffer **buffer, int ignore_change); +void ff_ass_process_chunk(FF_ASS_Renderer * assRenderer, const char *ass_line, float begin, float end); +void ff_ass_flush_events(FF_ASS_Renderer * assRenderer); + +#endif /* ff_ass_steam_renderer_h */ diff --git a/ijkmedia/ijkplayer/ff_ffmsg.h b/ijkmedia/ijkplayer/ff_ffmsg.h index b383789aaf..2bb58a6890 100755 --- a/ijkmedia/ijkplayer/ff_ffmsg.h +++ b/ijkmedia/ijkplayer/ff_ffmsg.h @@ -50,8 +50,9 @@ #define FFP_MSG_BUFFERING_TIME_UPDATE 504 /* arg1 = cached duration in milliseconds, arg2 = high water mark */ #define FFP_MSG_SEEK_COMPLETE 600 /* arg1 = seek position, arg2 = error */ #define FFP_MSG_PLAYBACK_STATE_CHANGED 700 -#define FFP_MSG_TIMED_TEXT 800 +//#define FFP_MSG_TIMED_TEXT 800 #define FFP_MSG_SELECTED_STREAM_CHANGED 850 /* stream changed */ +#define FFP_MSG_SELECTING_STREAM_FAILED 851 /* select stream failed */ #define FFP_MSG_ACCURATE_SEEK_COMPLETE 900 /* arg1 = current position*/ //#define FFP_MSG_GET_IMG_STATE 1000 /* arg1 = timestamp, arg2 = result code, obj = file name*/ diff --git a/ijkmedia/ijkplayer/ff_ffplay.c b/ijkmedia/ijkplayer/ff_ffplay.c index 5194b10811..f155f6b9a5 100755 --- a/ijkmedia/ijkplayer/ff_ffplay.c +++ b/ijkmedia/ijkplayer/ff_ffplay.c @@ -75,9 +75,12 @@ #include "ff_frame_queue.h" #include "ff_packet_list.h" #include "ff_subtitle.h" +#include "ijksdl/ijksdl_gpu.h" #include #if defined(__ANDROID__) #include "ijksoundtouch/ijksoundtouch_wrap.h" +#elif defined(__APPLE__) +#include #endif #ifndef AV_CODEC_FLAG2_FAST @@ -401,52 +404,65 @@ static void free_picture(Frame *vp) } } -// FFP_MERGE: realloc_texture -// FFP_MERGE: calculate_display_rect -// FFP_MERGE: upload_texture -// FFP_MERGE: video_image_display - -static void update_subtitle_text(FFPlayer *ffp,const char *str) +//-1: no change. 0:close current. 1:opened new +static int ff_apply_subtitle_stream_change(FFPlayer *ffp) { - //update subtitle,save into vout's opaque - if (ffp->vout->update_subtitle) { - SDL_LockMutex(ffp->vout->mutex); - ffp->vout->update_subtitle(ffp->vout, str); - SDL_UnlockMutex(ffp->vout->mutex); + VideoState *is = ffp->is; + int update_stream; + int r = ff_sub_update_stream_if_need(is->ffSub, &update_stream); + if (r > 0) { + AVCodecContext * avctx = ff_sub_get_avctx(is->ffSub); + ffp_set_subtitle_codec_info(ffp, AVCODEC_MODULE_NAME, avcodec_get_name(avctx->codec_id)); + int stream = ff_sub_get_current_stream(is->ffSub, NULL); + ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_TIMEDTEXT_STREAM, stream); + ffp_notify_msg1(ffp, FFP_MSG_SELECTED_STREAM_CHANGED); + + int type = ff_sub_current_stream_type(is->ffSub); + if (type == 2) { + //seek the extra subtitle + float sec = ffp_get_current_position_l(ffp) / 1000 - 1; + float delay = ff_sub_get_delay(is->ffSub); + //when exchang stream force seek stream instead of ff_sub_set_delay + ff_sub_seek_to(is->ffSub, delay, sec); + } + } else if (r == 0) { + ffp_set_subtitle_codec_info(ffp, AVCODEC_MODULE_NAME, ""); + ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_TIMEDTEXT_STREAM, -1); + ffp_notify_msg1(ffp, FFP_MSG_SELECTED_STREAM_CHANGED); + } else if (r < -1) { + ffp_set_subtitle_codec_info(ffp, AVCODEC_MODULE_NAME, ""); + ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_TIMEDTEXT_STREAM, -1); + ffp_notify_msg1(ffp, FFP_MSG_SELECTED_STREAM_CHANGED); + ffp_notify_msg3(ffp, FFP_MSG_SELECTING_STREAM_FAILED, update_stream, r); } - ffp_notify_msg4(ffp, FFP_MSG_TIMED_TEXT, 0, 0, (void *)str, (int)strlen(str) + 1); + return r; } -static void update_subtitle_pict(FFPlayer *ffp, const AVSubtitleRect *bmp) -{ - //update subtitle by bitmap, save into vout's opaque - if (ffp->vout->update_subtitle_picture) { - SDL_LockMutex(ffp->vout->mutex); - ffp->vout->update_subtitle_picture(ffp->vout, bmp); - SDL_UnlockMutex(ffp->vout->mutex); - } - ffp_notify_msg1(ffp, FFP_MSG_TIMED_TEXT); -} +// FFP_MERGE: realloc_texture +// FFP_MERGE: calculate_display_rect +// FFP_MERGE: upload_texture +// FFP_MERGE: video_image_display static void video_image_display2(FFPlayer *ffp) { VideoState *is = ffp->is; Frame *vp = frame_queue_peek_last(&is->pictq); if (vp->bmp) { + SDL_TextureOverlay *sub_overlay = NULL; if (is->step_on_seeking) { - update_subtitle_text(ffp, ""); + //ignore subtitle } else if (is->ffSub) { - char *text = NULL; - AVSubtitleRect *bmp = NULL; - if (ff_sub_fetch_frame(is->ffSub, vp->pts, &text, &bmp) >= 0) { - if (text) { - update_subtitle_text(ffp, text); - av_free(text); - } else if (bmp) { - update_subtitle_pict(ffp, bmp); + int r = ff_apply_subtitle_stream_change(ffp); + //has stream + if (ffp->subtitle && ff_sub_get_current_stream(is->ffSub, NULL) >= 0) { + int got = ff_sub_get_texture(is->ffSub, vp->pts, ffp->gpu, &sub_overlay); + //when got equal to -100 means the ass subtitle frame not ready,need retry! + if (!sub_overlay && (r > 0 || got == -100) && is->pause_req) { + //give one more chance + is->force_refresh_sub_changed = 1; + av_usleep(3*1000); + return; } - } else { - update_subtitle_text(ffp, ""); } } @@ -459,7 +475,9 @@ static void video_image_display2(FFPlayer *ffp) SDL_Delay(20); } } - SDL_VoutDisplayYUVOverlay(ffp->vout, vp->bmp); + SDL_VoutDisplayYUVOverlay(ffp->vout, vp->bmp, sub_overlay); + SDL_TextureOverlay_Release(&sub_overlay); + ffp->stat.vfps = SDL_SpeedSamplerAdd(&ffp->vfps_sampler, FFP_SHOW_VFPS_FFPLAY, "vfps[ffplay]"); if (!ffp->first_video_frame_rendered) { ffp->first_video_frame_rendered = 1; @@ -541,28 +559,26 @@ static void stream_component_close(FFPlayer *ffp, int stream_index) static void stream_close(FFPlayer *ffp) { + av_log(NULL, AV_LOG_INFO, "stream_close will close\n"); VideoState *is = ffp->is; /* XXX: use a special url_shutdown call to abort parse cleanly */ is->abort_request = 1; packet_queue_abort(&is->videoq); packet_queue_abort(&is->audioq); ff_sub_abort(is->ffSub); - av_log(NULL, AV_LOG_DEBUG, "wait for read_tid\n"); SDL_WaitThread(is->read_tid, NULL); - /* close each stream */ if (is->audio_stream >= 0) stream_component_close(ffp, is->audio_stream); if (is->video_stream >= 0) stream_component_close(ffp, is->video_stream); - ff_sub_close_current(is->ffSub); - avformat_close_input(&is->ic); av_log(NULL, AV_LOG_DEBUG, "wait for video_refresh_tid\n"); SDL_WaitThread(is->video_refresh_tid, NULL); - + ff_sub_destroy(&is->ffSub); + packet_queue_destroy(&is->videoq); packet_queue_destroy(&is->audioq); @@ -570,7 +586,6 @@ static void stream_close(FFPlayer *ffp) frame_queue_destory(&is->pictq); frame_queue_destory(&is->sampq); - ff_sub_destroy(&is->ffSub); SDL_DestroyCond(is->audio_accurate_seek_cond); SDL_DestroyCond(is->video_accurate_seek_cond); SDL_DestroyCond(is->continue_read_thread); @@ -591,6 +606,7 @@ static void stream_close(FFPlayer *ffp) av_free(is->filename); av_free(is); ffp->is = NULL; + av_log(NULL, AV_LOG_INFO, "stream_close did close\n"); } // FFP_MERGE: do_exit @@ -890,7 +906,14 @@ static void video_refresh(FFPlayer *opaque, double *remaining_time) FFPlayer *ffp = opaque; VideoState *is = ffp->is; double time; - + + //applay subtitle preference changed when the palyer was paused. + if (is->paused && is->force_refresh_sub_changed) { + is->force_refresh_sub_changed = 0; + video_display2(ffp); + return; + } + if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime) check_external_clock_speed(is); @@ -905,6 +928,7 @@ static void video_refresh(FFPlayer *opaque, double *remaining_time) if (ffp->audio_disable && ffp->display_disable) { frame_queue_next(&is->pictq); + ff_sub_drop_old_frames(is->ffSub); return; } @@ -944,6 +968,7 @@ static void video_refresh(FFPlayer *opaque, double *remaining_time) //when fast seek,we want update video frame,no drop frame. but we can't identify seek is continuously. if (vp->serial != is->videoq.serial) { frame_queue_next(&is->pictq); + ff_sub_drop_old_frames(is->ffSub); goto retry; } @@ -1005,6 +1030,7 @@ static void video_refresh(FFPlayer *opaque, double *remaining_time) duration = vp_duration(is, vp, nextvp); if(!is->step && (ffp->framedrop > 0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration) { frame_queue_next(&is->pictq); + ff_sub_drop_old_frames(is->ffSub); goto retry; } } @@ -1112,19 +1138,10 @@ static void alloc_picture(FFPlayer *ffp, int src_format) #ifdef FFP_MERGE video_open(is, vp); #endif - -#ifdef __APPLE__ - vp->bmp = SDL_Vout_CreateOverlay_Apple(vp->width, - vp->height, - src_format, - ffp->cvpixelbufferpool, - ffp->vout); -#else vp->bmp = SDL_Vout_CreateOverlay(vp->width, vp->height, src_format, ffp->vout); -#endif #ifdef FFP_MERGE if (vp->format == AV_PIX_FMT_YUV420P) sdl_format = SDL_PIXELFORMAT_YV12; @@ -1403,6 +1420,7 @@ static int queue_picture(FFPlayer *ffp, AVFrame *src_frame, double pts, double d break; default: ALOGE("unknow overly format:%.4s(0x%x)\n", (char*)&overlay_format, overlay_format); + return -1000; break; } @@ -1573,6 +1591,7 @@ static int configure_filtergraph(AVFilterGraph *graph, const char *filtergraph, AVFilterInOut *outputs = NULL, *inputs = NULL; if (filtergraph) { + av_log(NULL, AV_LOG_INFO, "Video filtergraph:%s", filtergraph); outputs = avfilter_inout_alloc(); inputs = avfilter_inout_alloc(); if (!outputs || !inputs) { @@ -2056,8 +2075,6 @@ static int ffplay_video_thread(void *arg) return AVERROR(ENOMEM); } - SDL_VoutSetOverlayFormat(ffp->vout, ffp->overlay_format); - for (;;) { ret = get_video_frame(ffp, frame); if (ret < 0) @@ -2074,7 +2091,7 @@ static int ffplay_video_thread(void *arg) || last_vfilter_idx != is->vfilter_idx) { SDL_LockMutex(ffp->vf_mutex); ffp->vf_changed = 0; - av_log(NULL, AV_LOG_DEBUG, + av_log(NULL, AV_LOG_INFO, "Video frame changed from size:%dx%d format:%s serial:%d to size:%dx%d format:%s serial:%d\n", last_w, last_h, (const char *)av_x_if_null(av_get_pix_fmt_name(last_format), "none"), last_serial, @@ -2173,6 +2190,7 @@ static void update_sample_display(FFPlayer *ffp, uint8_t *samples, int samples_s return; } + //以下计算以 2 bytes(int16_t) 为单位 int size, len; size = samples_size / sizeof(int16_t); while (size > 0) { @@ -2187,23 +2205,22 @@ static void update_sample_display(FFPlayer *ffp, uint8_t *samples, int samples_s size -= len; } + //以下计算以 byte(int8_t) 为单位 if (ffp->audio_samples_callback) { - float factor = FFMAX(is->audio_src.freq / 44100.0, 0.5); - int windowSize = FFMIN(factor * 1024 * is->audio_src.ch_layout.nb_channels, SAMPLE_ARRAY_SIZE / 2); + int windowSize = 2048; int i = 0; - for (; i < is->sample_array_index / windowSize; i++) { + for (; i < (is->sample_array_index * 2) / windowSize; i++) { ffp->audio_samples_callback( ffp->inject_opaque, - (int16_t*)is->sample_array + i * windowSize, + (int16_t*)((int8_t*)is->sample_array + i * windowSize), windowSize, - is->audio_src.freq, + is->audio_tgt.freq, is->audio_src.ch_layout.nb_channels ); } if (i > 0) { - i--; - memcpy(is->sample_array, is->sample_array + windowSize * i, (is->sample_array_index - windowSize * i) * sizeof(int16_t)); - is->sample_array_index -= windowSize * i; + memcpy(is->sample_array, (int8_t*)is->sample_array + windowSize * i, is->sample_array_index * 2 - windowSize * i); + is->sample_array_index -= (windowSize * i) / 2; } } } @@ -2876,10 +2893,13 @@ static int stream_component_open(FFPlayer *ffp, int stream_index) AVChannelLayout ch_layout = { 0 }; int ret = 0; int stream_lowres = ffp->lowres; - AVStream *st = ic->streams[stream_index]; if (stream_index < 0 || stream_index >= ic->nb_streams) return -1; + + AVStream *st = ic->streams[stream_index]; + assert(st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE); + avctx = avcodec_alloc_context3(NULL); if (!avctx) return AVERROR(ENOMEM); @@ -2892,9 +2912,8 @@ static int stream_component_open(FFPlayer *ffp, int stream_index) codec = avcodec_find_decoder(avctx->codec_id); switch (avctx->codec_type) { - case AVMEDIA_TYPE_AUDIO : is->last_audio_stream = stream_index; forced_codec_name = ffp->audio_codec_name; break; - case AVMEDIA_TYPE_SUBTITLE: is->last_subtitle_stream = stream_index; forced_codec_name = ffp->subtitle_codec_name; break; - case AVMEDIA_TYPE_VIDEO : is->last_video_stream = stream_index; forced_codec_name = ffp->video_codec_name; break; + case AVMEDIA_TYPE_AUDIO : forced_codec_name = ffp->audio_codec_name; break; + case AVMEDIA_TYPE_VIDEO : forced_codec_name = ffp->video_codec_name; break; default: break; } if (forced_codec_name) @@ -2905,7 +2924,7 @@ static int stream_component_open(FFPlayer *ffp, int stream_index) else av_log(NULL, AV_LOG_WARNING, "No codec could be found with id %s\n", avcodec_get_name(avctx->codec_id)); ret = AVERROR(EINVAL); - ffp_notify_msg2(ffp, FFP_MSG_NO_CODEC_FOUND,avctx->codec_id); + ffp_notify_msg2(ffp, FFP_MSG_NO_CODEC_FOUND, avctx->codec_id); goto fail; } @@ -3084,22 +3103,6 @@ static int stream_component_open(FFPlayer *ffp, int stream_index) } _ijkmeta_set_stream(ffp, avctx->codec_type, stream_index); break; - case AVMEDIA_TYPE_SUBTITLE: - if (!ffp->subtitle) - break; - //maybe already has ex subtile - if (0 == ff_sub_current_stream_type(is->ffSub, NULL)) { - ret = ff_sub_open_component(is->ffSub, stream_index, NULL, avctx); - if (ret == 0) { - ffp_set_subtitle_codec_info(ffp, AVCODEC_MODULE_NAME, avcodec_get_name(avctx->codec_id)); - _ijkmeta_set_stream(ffp, avctx->codec_type, stream_index); - goto out; - } else { - //if failed, freed context internal. - goto out; - } - } - break; default: break; } @@ -3165,6 +3168,7 @@ static AVDictionary **setup_find_stream_info_opts(AVFormatContext *s, return opts; } +int ffp_apply_subtitle_preference(FFPlayer *ffp); /* this thread gets the stream from the disk or the network */ static int read_thread(void *arg) { @@ -3348,7 +3352,6 @@ static int read_thread(void *arg) st_index[type] = i; // choose first h264 - if (type == AVMEDIA_TYPE_VIDEO) { enum AVCodecID codec_id = st->codecpar->codec_id; video_stream_count++; @@ -3400,8 +3403,7 @@ static int read_thread(void *arg) } //when open audio stream failed or no audio stream use video as av sync master. - if (ret || st_index[AVMEDIA_TYPE_AUDIO] == -1) - { + if (ret || st_index[AVMEDIA_TYPE_AUDIO] == -1) { ffp->av_sync_type = AV_SYNC_VIDEO_MASTER; is->av_sync_type = ffp->av_sync_type; } @@ -3412,11 +3414,23 @@ static int read_thread(void *arg) } if (is->show_mode == SHOW_MODE_NONE) is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT; - //tell subtitle stream is ready. - ff_sub_stream_ic_ready(is->ffSub, ic); - if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) { - stream_component_open(ffp, st_index[AVMEDIA_TYPE_SUBTITLE]); + //tell subtitle stream is ready. + if (is->video_st) { + AVCodecParameters *codecpar = is->video_st->codecpar; + + int v_width = codecpar->width; + if (codecpar->sample_aspect_ratio.num > 0 && codecpar->sample_aspect_ratio.den > 0) { + float ratio = 1.0 * codecpar->sample_aspect_ratio.num / codecpar->sample_aspect_ratio.den; + v_width = (int)(v_width * ratio); + } + ff_sub_stream_ic_ready(is->ffSub, ic, v_width, codecpar->height); + if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) { + AVStream *st = ic->streams[st_index[AVMEDIA_TYPE_SUBTITLE]]; + st->discard = AVDISCARD_DEFAULT; + ff_sub_record_need_select_stream(is->ffSub, st_index[AVMEDIA_TYPE_SUBTITLE]); + ffp_apply_subtitle_preference(ffp); + } } ffp_notify_msg1(ffp, FFP_MSG_COMPONENT_OPEN); @@ -3545,7 +3559,7 @@ static int read_thread(void *arg) } packet_queue_flush(&is->videoq); } - ff_inSub_packet_queue_flush(is->ffSub); + ff_sub_packet_queue_flush(is->ffSub); if (is->seek_flags & AVSEEK_FLAG_BYTE) { set_clock(&is->extclk, NAN, 0); } else { @@ -3557,13 +3571,12 @@ static int read_thread(void *arg) is->latest_seek_load_start_at = av_gettime(); } - if (ff_sub_current_stream_type(is->ffSub, NULL) == 2) { - //seek the extra subtitle - int sec = (int)fftime_to_seconds(seek_target); - float delay = ff_sub_get_delay(is->ffSub); - ff_sub_seek_to(is->ffSub, delay, sec); - //ff_sub_set_delay(is->ffSub, delay, sec); - } + //seek the extra subtitle + int sec = (int)fftime_to_seconds(seek_target); + float delay = ff_sub_get_delay(is->ffSub); + ff_sub_seek_to(is->ffSub, delay, sec); + //ff_sub_set_delay(is->ffSub, delay, sec); + //seek 后降低水位线,让播放器更快满足条件 ffp->dcc.current_high_water_mark_in_ms = ffp->dcc.first_high_water_mark_in_ms; is->seek_req = 0; @@ -3724,7 +3737,10 @@ static int read_thread(void *arg) packet_queue_put_nullpacket(&is->videoq, pkt, is->video_stream); if (is->audio_stream >= 0) packet_queue_put_nullpacket(&is->audioq, pkt, is->audio_stream); - ff_sub_put_null_packet(is->ffSub, pkt, ff_sub_get_opened_stream_idx(is->ffSub)); + int st = ff_sub_get_current_stream(is->ffSub, NULL); + if (st >= 0) { + ff_sub_put_null_packet(is->ffSub, pkt, st); + } is->eof = 1; } if (pb_error) { @@ -3732,7 +3748,10 @@ static int read_thread(void *arg) packet_queue_put_nullpacket(&is->videoq, pkt, is->video_stream); if (is->audio_stream >= 0) packet_queue_put_nullpacket(&is->audioq, pkt, is->audio_stream); - ff_sub_put_null_packet(is->ffSub, pkt, ff_sub_get_opened_stream_idx(is->ffSub)); + int st = ff_sub_get_current_stream(is->ffSub, NULL); + if (st >= 0) { + ff_sub_put_null_packet(is->ffSub, pkt, st); + } is->eof = 1; ffp->error = pb_error; av_log(ffp, AV_LOG_ERROR, "av_read_frame error: %s\n", ffp_get_error_string(ffp->error)); @@ -3772,34 +3791,40 @@ static int read_thread(void *arg) is->audioq.serial++; //packet_queue_put(&is->audioq, &flush_pkt); } - if (ff_sub_get_opened_stream_idx(is->ffSub) >= 0) { - //TODO test here - //is->subtitleq.serial++; - //packet_queue_put(&is->subtitleq, &flush_pkt); - } if (is->video_stream >= 0) { is->videoq.serial++; //packet_queue_put(&is->videoq, &flush_pkt); } } - + AVStream *st = ic->streams[pkt->stream_index]; /* check if packet is in play range specified by user, then queue, otherwise discard */ - stream_start_time = ic->streams[pkt->stream_index]->start_time; + stream_start_time = st->start_time; pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts; pkt_in_play_range = ffp->duration == AV_NOPTS_VALUE || (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) * av_q2d(ic->streams[pkt->stream_index]->time_base) - (double)(ffp->start_time != AV_NOPTS_VALUE ? ffp->start_time : 0) / 1000000 <= ((double)ffp->duration / 1000000); - if (pkt->stream_index == is->audio_stream && pkt_in_play_range) { - packet_queue_put(&is->audioq, pkt); - } else if (pkt->stream_index == is->video_stream && pkt_in_play_range - && !(is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC))) { - packet_queue_put(&is->videoq, pkt); - } else if (pkt->stream_index == ff_sub_get_opened_stream_idx(is->ffSub) && pkt_in_play_range) { - ff_sub_put_packet(is->ffSub, pkt); - } else { + if (!pkt_in_play_range) { av_packet_unref(pkt); + } else { + int stream_index = pkt->stream_index; + if (stream_index == is->audio_stream) { + packet_queue_put(&is->audioq, pkt); + } else if (stream_index == is->video_stream + && !(is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC))) { + packet_queue_put(&is->videoq, pkt); + } else { + int sub_pending_stream; + int sub_stream = ff_sub_get_current_stream(is->ffSub, &sub_pending_stream); + if (stream_index == sub_stream && sub_pending_stream == -2) { + ff_sub_put_packet(is->ffSub, pkt); + } else if (stream_index == sub_pending_stream) { + ff_sub_put_packet_backup(is->ffSub, pkt); + } else { + av_packet_unref(pkt); + } + } } ffp_statistic_l(ffp); @@ -3858,9 +3883,8 @@ static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputForma is = av_mallocz(sizeof(VideoState)); if (!is) return NULL; - is->last_video_stream = is->video_stream = -1; - is->last_audio_stream = is->audio_stream = -1; - is->last_subtitle_stream = -1; + is->video_stream = -1; + is->audio_stream = -1; is->filename = av_strdup(filename); if (!is->filename) goto fail; @@ -3983,10 +4007,13 @@ static int video_refresh_thread(void *arg) if (remaining_time > 0.0) av_usleep((int)(int64_t)(remaining_time * 1000000.0)); remaining_time = REFRESH_RATE; - if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh || is->step_on_seeking)) + if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh || is->step_on_seeking || is->force_refresh_sub_changed)) video_refresh(ffp, &remaining_time); } - + //clean GLView's attach,because the attach retained sub_overlay; + //otherwise sub_overlay will be free in main thread! + SDL_VoutDisplayYUVOverlay(ffp->vout, NULL, NULL); + ff_sub_desctoy_objs(is->ffSub); return 0; } @@ -4201,6 +4228,7 @@ void ffp_destroy(FFPlayer *ffp) SDL_VoutFreeP(&ffp->vout); SDL_AoutFreeP(&ffp->aout); + SDL_GPUFreeP(&ffp->gpu); ffpipenode_free_p(&ffp->node_vdec); ffpipeline_free_p(&ffp->pipeline); ijkmeta_destroy_p(&ffp->meta); @@ -4214,7 +4242,6 @@ void ffp_destroy(FFPlayer *ffp) msg_queue_destroy(&ffp->msg_queue); - av_free(ffp); } @@ -4317,7 +4344,6 @@ void *ffp_set_inject_opaque(FFPlayer *ffp, void *opaque) return prev_weak_thiz; } - void ffp_set_option(FFPlayer *ffp, int opt_category, const char *name, const char *value) { if (!ffp) @@ -4344,26 +4370,6 @@ void ffp_set_option_intptr(FFPlayer *ffp, int opt_category, const char *name, ui AVDictionary **dict = ffp_get_opt_dict(ffp, opt_category); av_dict_set_intptr(dict, name, value, 0); } - -void ffp_set_overlay_format(FFPlayer *ffp, int chroma_fourcc) -{ -#warning TODO REVIEW - switch (chroma_fourcc) { - case SDL_FCC__GLES2: - case SDL_FCC_I420: - case SDL_FCC_J420: - case SDL_FCC_YV12: - case SDL_FCC_BGRA: - case SDL_FCC_BGR0: - case SDL_FCC_ARGB: - case SDL_FCC_0RGB: - ffp->overlay_format = chroma_fourcc; - break; - default: - av_log(ffp, AV_LOG_ERROR, "ffp_set_overlay_format: unknown chroma fourcc: %d\n", chroma_fourcc); - break; - } -} int ffp_get_video_codec_info(FFPlayer *ffp, char **codec_info) { @@ -4417,6 +4423,7 @@ static void ffp_show_version_int(FFPlayer *ffp, const char *module, unsigned ver (unsigned int)IJKVERSION_GET_MICRO(version)); } +#if CONFIG_AVFILTER static void *grow_array(void *array, int elem_size, int *size, int new_size) { if (new_size >= INT_MAX / elem_size) { @@ -4439,6 +4446,21 @@ static void *grow_array(void *array, int elem_size, int *size, int new_size) #define GROW_ARRAY(array, nb_elems)\ array = grow_array(array, sizeof(*array), &nb_elems, nb_elems + 1) +static void resetVideoFilter(FFPlayer *ffp, const char *filter) { + if (filter) { + av_freep(&ffp->vfilters_list); + VideoState *is = ffp->is; + is->vfilter_idx = 0; + GROW_ARRAY(ffp->vfilters_list, ffp->nb_vfilters); + if (ffp->vfilters_list == NULL) { + return; + } + ffp->vfilters_list[ffp->nb_vfilters - 1] = filter; + ffp->vf_changed = 1; + } +} +#endif + int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name) { assert(ffp); @@ -4480,15 +4502,11 @@ int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name) if (!ffp->aout) return -1; } - + + ffp->vout->cvpixelbufferpool = ffp->cvpixelbufferpool; + ffp->vout->overlay_format = ffp->overlay_format; #if CONFIG_AVFILTER - if (ffp->vfilter0) { - GROW_ARRAY(ffp->vfilters_list, ffp->nb_vfilters); - if (ffp->vfilters_list == NULL) { - return EIJK_OUT_OF_MEMORY; - } - ffp->vfilters_list[ffp->nb_vfilters - 1] = ffp->vfilter0; - } + resetVideoFilter(ffp, ffp->vfilter0); #endif VideoState *is = stream_open(ffp, file_name, NULL); @@ -4973,59 +4991,10 @@ int ffp_get_video_rotate_degrees(FFPlayer *ffp) return theta; } -static int ffp_set_sub_stream_selected(FFPlayer *ffp, int stream, int selected) -{ - if (!ffp->subtitle) - return -1; - VideoState *is = ffp->is; - - int closed = 0,opened = 0; - int current = ff_sub_get_opened_stream_idx(is->ffSub); - if (selected && current == stream) { - return 1; - } - if (current != -1) { - ff_sub_close_current(is->ffSub); - closed = 1; - } - - if (selected) { - int type = 0; - if (ff_sub_isInternal_stream(is->ffSub, stream)) { - type = 1; - } else if (ff_sub_isExternal_stream(is->ffSub, stream)) { - type = 2; - } else { - return -2; - } - - if (type == 1) { - if (stream_component_open(ffp, stream) == 0) { - opened = 1; - } else { - av_log(NULL, AV_LOG_ERROR, "sub stream open failed:%d",stream); - } - } else { - if (ff_exSub_open_stream(is->ffSub, stream) == 0) { - opened = 1; - } else { - av_log(NULL, AV_LOG_ERROR, "ex sub stream open failed:%d",stream); - } - } - } - if (closed || opened) { - int idx = opened ? stream : -1; - _ijkmeta_set_stream(ffp, AVMEDIA_TYPE_SUBTITLE, idx); - ffp_notify_msg1(ffp, FFP_MSG_SELECTED_STREAM_CHANGED); - update_subtitle_text(ffp, ""); - } - return 0; -} - //return value : //err: less than zero; -//ok: zero; -//already ok: greater than zero; +//changed: greater than zero; +//already ok: zero static int ffp_set_internal_stream_selected(FFPlayer *ffp, int stream, int selected) { VideoState *is = ffp->is; @@ -5036,41 +5005,76 @@ static int ffp_set_internal_stream_selected(FFPlayer *ffp, int stream, int selec ic = is->ic; if (!ic) return -1; - + int type = ic->streams[stream]->codecpar->codec_type; - - int closed = 0,opened = 0; - + int opened = 0,closed = 0; + switch (type) { case AVMEDIA_TYPE_VIDEO: + { if (selected && stream == is->video_stream) { - return 1; + av_log(ffp, AV_LOG_INFO, "can't reselected video stream : %d\n", stream); + return 0; } if (is->video_stream >= 0) { + av_log(ffp, AV_LOG_INFO, "will close video stream : %d\n", stream); stream_component_close(ffp, is->video_stream); closed = 1; } if (selected) { + av_log(ffp, AV_LOG_INFO, "will open video stream : %d\n", stream); opened = stream_component_open(ffp, stream) == 0; } + } break; case AVMEDIA_TYPE_AUDIO: + { if (selected && stream == is->audio_stream) { - return 1; + av_log(ffp, AV_LOG_INFO, "can't reselected auido stream : %d\n", stream); + return 0; } if (is->audio_stream >= 0) { + av_log(ffp, AV_LOG_INFO, "will close audio stream : %d\n", stream); stream_component_close(ffp, is->audio_stream); closed = 1; } if (selected) { + av_log(ffp, AV_LOG_INFO, "will open audio stream : %d\n", stream); //keep play rate and volume. ffp->pf_playback_rate_changed = 1; ffp->pf_playback_volume_changed = 1; opened = stream_component_open(ffp, stream) == 0; } + } break; case AVMEDIA_TYPE_SUBTITLE: - return ffp_set_sub_stream_selected(ffp, stream, selected); + { + int current = ff_sub_get_current_stream(is->ffSub, NULL); + if (selected && stream == current) { + av_log(ffp, AV_LOG_INFO, "can't reselected subtitle stream : %d\n", stream); + return 0; + } + + if (current >= 0 && current < ic->nb_streams) { + AVStream *st = ic->streams[current]; + st->discard = AVDISCARD_ALL; + } + + if (selected) { + //let av_read_frame not discard + AVStream *st = ic->streams[stream]; + st->discard = AVDISCARD_DEFAULT; + } + + int r = ff_sub_record_need_select_stream(is->ffSub, selected ? stream : -1); + if (r == 1) { + ffp_apply_subtitle_preference(ffp); + if (is->paused) { + is->force_refresh_sub_changed = 1; + } + } + return r; + } default: av_log(ffp, AV_LOG_ERROR, "select invalid stream %d of video type %d\n", stream, codecpar->codec_type); return -1; @@ -5081,11 +5085,13 @@ static int ffp_set_internal_stream_selected(FFPlayer *ffp, int stream, int selec _ijkmeta_set_stream(ffp, type, idx); ffp_notify_msg1(ffp, FFP_MSG_SELECTED_STREAM_CHANGED); } - - return 0; + return 1; } -//return value greater than 0 means top caller need seek; +//return value : +//err: less than zero; +//already ok: zero +//greater than zero;means top caller need seek; int ffp_set_stream_selected(FFPlayer *ffp, int stream, int selected) { VideoState *is = ffp->is; @@ -5096,35 +5102,21 @@ int ffp_set_stream_selected(FFPlayer *ffp, int stream, int selected) return -2; if (stream >= 0 && stream < ic->nb_streams) { - int r = ffp_set_internal_stream_selected(ffp, stream, selected); - if (r == 0) { - return 1; - } else if (r > 0){ - return 0; - } else { - return r; - } - } else if (ff_sub_isExternal_stream(is->ffSub, stream)) { - int r = ffp_set_sub_stream_selected(ffp, stream, selected); - if (r == 0 && selected) { - //seek the extra subtitle - float sec = ffp_get_current_position_l(ffp) / 1000 - 1; - float delay = ff_sub_get_delay(is->ffSub); - //when exchang stream force seek stream instead of ff_sub_set_delay - ff_sub_seek_to(is->ffSub, delay, sec); - enum AVCodecID cid = ff_sub_get_codec_id(is->ffSub); - if (cid != AV_CODEC_ID_NONE) { - ffp_set_subtitle_codec_info(ffp, AVCODEC_MODULE_NAME, avcodec_get_name(cid)); + return ffp_set_internal_stream_selected(ffp, stream, selected); + } else { + int r = ff_sub_record_need_select_stream(is->ffSub, selected ? stream : -1); + if (r == 1) { + ffp_apply_subtitle_preference(ffp); + if (is->paused) { + is->force_refresh_sub_changed = 1; } return 0; - } else if (r > 0){ - return 0; + } else if (r == 0) { + av_log(ffp, AV_LOG_INFO, "keep current selected ex subtile stream index: %d\n", stream); } else { - return r; + av_log(ffp, AV_LOG_INFO, "can't selecet ext stream index: %d\n", stream); } - } else { - av_log(ffp, AV_LOG_ERROR, "can't selecet invalid stream index: %d\n", stream); - return -3; + return r; } } @@ -5179,8 +5171,8 @@ int64_t ffp_get_property_int64(FFPlayer *ffp, int id, int64_t default_value) if (!ffp || !ffp->is) return default_value; VideoState *is = ffp->is; - int idx = ff_sub_get_opened_stream_idx(is->ffSub); - if (idx != -1) { + int idx = ff_sub_get_current_stream(is->ffSub, NULL); + if (idx >= 0) { return idx; } else { return default_value; @@ -5319,36 +5311,49 @@ float ffp_get_subtitle_extra_delay(FFPlayer *ffp) //add + active int ffp_add_active_external_subtitle(FFPlayer *ffp, const char *file_name) { +#if CONFIG_AVFILTER + //use subtitles filter + char buffer[1024] = { 0 }; + sprintf(buffer, "subtitles=%s", file_name); + resetVideoFilter(ffp, av_strdup(buffer)); + return 1; +#endif + VideoState *is = ffp->is; - if (ff_exSub_check_file_added(file_name, is->ffSub) == 1) { - return 1; - } else { - ff_sub_close_current(is->ffSub); - int ret = ff_exSub_add_active_subtitle(is->ffSub, file_name, ffp->meta); - if (ret == 0) { - //seek the extra subtitle - float sec = ffp_get_current_position_l(ffp) / 1000 - 1; - float delay = ff_sub_get_delay(is->ffSub); - ff_sub_seek_to(is->ffSub, delay, sec); - ffp_notify_msg1(ffp, FFP_MSG_SELECTED_STREAM_CHANGED); - enum AVCodecID cid = ff_sub_get_codec_id(is->ffSub); - if (cid != AV_CODEC_ID_NONE) { - ffp_set_subtitle_codec_info(ffp, AVCODEC_MODULE_NAME, avcodec_get_name(cid)); + + int idx = -1; + IjkMediaMeta *stream_meta = NULL; + int r = ff_sub_add_ex_subtitle(is->ffSub, file_name, &stream_meta, &idx); + if (r == 1) { + //exist + } else if (r == 0) { + ijkmeta_append_child_l(ffp->meta, stream_meta); + //succ,not send STREAM_CHANGED msg because would send after selected the new stream int vout thread. + int ret = ff_sub_record_need_select_stream(is->ffSub, idx); + if (ret == 1) { + ffp_apply_subtitle_preference(ffp); + if (is->paused) { + is->force_refresh_sub_changed = 1; } } - return ret; + return r; + } else { + //fail } + return r; } //add only int ffp_addOnly_external_subtitle(FFPlayer *ffp, const char *file_name) { VideoState *is = ffp->is; - int ret = ff_exSub_addOnly_subtitle(is->ffSub, file_name, ffp->meta); - if (ret == 0) { + IjkMediaMeta *stream_meta = NULL; + int r = ff_sub_add_ex_subtitle(is->ffSub, file_name, &stream_meta, NULL); + if (r == 0) { + ijkmeta_append_child_l(ffp->meta, stream_meta); ffp_notify_msg1(ffp, FFP_MSG_SELECTED_STREAM_CHANGED); } - return ret; + return r; } //add only @@ -5359,8 +5364,9 @@ int ffp_addOnly_external_subtitles(FFPlayer *ffp, const char *file_names [], int for(int i = 0; i < count; i++) { const char *file = file_names[i]; if (file) { - int added = ff_exSub_addOnly_subtitle(is->ffSub, file, ffp->meta); - if (added == 0) { + IjkMediaMeta *stream_meta = NULL; + if (ff_sub_add_ex_subtitle(is->ffSub, file, &stream_meta, NULL) == 0) { + ijkmeta_append_child_l(ffp->meta, stream_meta); ret ++; } } else { @@ -5399,7 +5405,7 @@ void ffp_set_audio_sample_observer(FFPlayer *ffp, ijk_audio_samples_callback cb) ffp->audio_samples_callback = cb; } -void ffp_set_enable_accurate_seek(FFPlayer *ffp,int open) +void ffp_set_enable_accurate_seek(FFPlayer *ffp, int open) { if (!ffp || !ffp->is) { return; @@ -5410,3 +5416,25 @@ void ffp_set_enable_accurate_seek(FFPlayer *ffp,int open) } SDL_UnlockMutex(ffp->is->accurate_seek_mutex); } + +int ffp_apply_subtitle_preference(FFPlayer *ffp) +{ + if (!ffp || !ffp->is) { + return 0; + } + return ff_update_sub_preference(ffp->is->ffSub, &ffp->sp); +} + +void ffp_set_subtitle_preference(FFPlayer *ffp, IJKSDLSubtitlePreference* sp) +{ + if (!ffp || !sp) { + return; + } + + ffp->sp = *sp; + int r = ffp_apply_subtitle_preference(ffp); + //if subtitle preference changed and the player is paused,record need refresh vout + if (r && ffp->is && ffp->is->paused) { + ffp->is->force_refresh_sub_changed = 1; + } +} diff --git a/ijkmedia/ijkplayer/ff_ffplay.h b/ijkmedia/ijkplayer/ff_ffplay.h index a1439fa7c6..e54aa5aba9 100644 --- a/ijkmedia/ijkplayer/ff_ffplay.h +++ b/ijkmedia/ijkplayer/ff_ffplay.h @@ -123,4 +123,6 @@ void ffp_set_audio_sample_observer(FFPlayer *ffp, ijk_audio_samples_callbac void ffp_set_enable_accurate_seek(FFPlayer *ffp,int open); /* step to next frame */ void ffp_step_to_next_frame(FFPlayer *ffp); +/* set subtitle preference*/ +void ffp_set_subtitle_preference(FFPlayer *ffp, IJKSDLSubtitlePreference* sp); #endif diff --git a/ijkmedia/ijkplayer/ff_ffplay_def.c b/ijkmedia/ijkplayer/ff_ffplay_def.c index 015cc55d14..4d5874b494 100644 --- a/ijkmedia/ijkplayer/ff_ffplay_def.c +++ b/ijkmedia/ijkplayer/ff_ffplay_def.c @@ -30,6 +30,11 @@ int decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond int decoder_start(Decoder *d, int (*fn)(void *), void *arg, const char *name) { packet_queue_start(d->queue); + if (d->pkt_serial != d->queue->serial) { + av_log(NULL, AV_LOG_INFO, "correct decoder serial from %d to %d\n",d->pkt_serial,d->queue->serial); + d->pkt_serial = d->queue->serial; + } + d->decoder_tid = SDL_CreateThreadEx(&d->_decoder_tid, fn, arg, name); if (!d->decoder_tid) { av_log(NULL, AV_LOG_ERROR, "SDL_CreateThread(): %s\n", SDL_GetError()); diff --git a/ijkmedia/ijkplayer/ff_ffplay_def.h b/ijkmedia/ijkplayer/ff_ffplay_def.h index 197730af3d..da5b11a770 100755 --- a/ijkmedia/ijkplayer/ff_ffplay_def.h +++ b/ijkmedia/ijkplayer/ff_ffplay_def.h @@ -66,6 +66,7 @@ #include "ff_ffpipenode.h" #include "ijkmeta.h" #include "ijkavformat/ijklas.h" +#include "ff_subtitle_def.h" #define DEFAULT_HIGH_WATER_MARK_IN_BYTES (256 * 1024) #define SALTATION_RETURN_VALUE 1000 @@ -160,8 +161,6 @@ typedef struct PacketQueue { SDL_cond *cond; MyAVPacketList *recycle_pkt; int recycle_count; - int alloc_count; - int is_buffer_indicator; } PacketQueue; @@ -169,9 +168,9 @@ typedef struct PacketQueue { #define VIDEO_PICTURE_QUEUE_SIZE_MIN (3) #define VIDEO_PICTURE_QUEUE_SIZE_MAX (16) #define VIDEO_PICTURE_QUEUE_SIZE_DEFAULT (VIDEO_PICTURE_QUEUE_SIZE_MIN) -#define SUBPICTURE_QUEUE_SIZE 16 +#define SUBPICTURE_QUEUE_SIZE 32 #define SAMPLE_QUEUE_SIZE 9 -#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE, FFMAX(VIDEO_PICTURE_QUEUE_SIZE_MAX, SUBPICTURE_QUEUE_SIZE)) +#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE, VIDEO_PICTURE_QUEUE_SIZE_MAX) #define VIDEO_MAX_FPS_DEFAULT 30 @@ -198,6 +197,7 @@ typedef struct Clock { typedef struct Frame { AVFrame *frame; AVSubtitle sub; + FFSubtitleBuffer *sub_list[SUB_REF_MAX_LEN]; int serial; double pts; /* presentation timestamp for the frame */ double duration; /* estimated duration of the frame */ @@ -212,7 +212,7 @@ typedef struct Frame { int height; int format; AVRational sar; - int uploaded; + int shown; } Frame; typedef struct FrameQueue { @@ -268,6 +268,7 @@ typedef struct VideoState { SDL_Thread _read_tid; const AVInputFormat *iformat; int abort_request; + int force_refresh_sub_changed;//changed sub prefercence or close sub. int force_refresh; int paused; int last_paused; @@ -369,8 +370,6 @@ typedef struct VideoState { AVFilterGraph *agraph; // audio filter graph #endif - int last_video_stream, last_audio_stream, last_subtitle_stream; - SDL_cond *continue_read_thread; /* extra fields */ @@ -595,7 +594,6 @@ typedef struct FFPlayer { int infinite_buffer; enum ShowMode show_mode; char *audio_codec_name; - char *subtitle_codec_name; char *video_codec_name; double rdftspeed; #ifdef FFP_MERGE @@ -624,6 +622,7 @@ typedef struct FFPlayer { /* extra fields */ SDL_Aout *aout; SDL_Vout *vout; + struct SDL_GPU *gpu; struct IJKFF_Pipeline *pipeline; struct IJKFF_Pipenode *node_vdec; @@ -710,6 +709,8 @@ typedef struct FFPlayer { LasPlayerStatistic las_player_statistic; ijk_audio_samples_callback audio_samples_callback; + + IJKSDLSubtitlePreference sp; } FFPlayer; #define fftime_to_seconds(ts) (av_rescale(ts, 1, AV_TIME_BASE)) diff --git a/ijkmedia/ijkplayer/ff_frame_queue.c b/ijkmedia/ijkplayer/ff_frame_queue.c index 2e4b188613..8e53b28bc7 100644 --- a/ijkmedia/ijkplayer/ff_frame_queue.c +++ b/ijkmedia/ijkplayer/ff_frame_queue.c @@ -11,7 +11,17 @@ void frame_queue_unref_item(Frame *vp) { av_frame_unref(vp->frame); SDL_VoutUnrefYUVOverlay(vp->bmp); - avsubtitle_free(&vp->sub); + + int count = 0; + while (count < SUB_REF_MAX_LEN) { + FFSubtitleBuffer *h = vp->sub_list[count]; + if (!h) { + break; + } + ff_subtitle_buffer_release(&h); + count++; + } + bzero(vp->sub_list, sizeof(vp->sub_list)); } int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last) @@ -68,6 +78,14 @@ Frame *frame_queue_peek_next(FrameQueue *f) return &f->queue[(f->rindex + f->rindex_shown + 1) % f->max_size]; } +Frame *frame_queue_peek_offset(FrameQueue *f, int offset) +{ + if (offset >= f->size) { + return NULL; + } + return &f->queue[(f->rindex + f->rindex_shown + offset) % f->max_size]; +} + Frame *frame_queue_peek_last(FrameQueue *f) { return &f->queue[f->rindex]; @@ -89,9 +107,38 @@ Frame *frame_queue_peek_writable(FrameQueue *f) return &f->queue[f->windex]; } +Frame *frame_queue_peek_writable_noblock(FrameQueue *f) +{ + SDL_LockMutex(f->mutex); + if (f->size >= f->max_size || f->pktq->abort_request) { + SDL_UnlockMutex(f->mutex); + return NULL; + } + SDL_UnlockMutex(f->mutex); + + return &f->queue[f->windex]; +} + +Frame *frame_queue_peek_pre_writable(FrameQueue *f) +{ + if (f->pktq->abort_request) + return NULL; + + SDL_LockMutex(f->mutex); + if (f->size < 1) { + SDL_UnlockMutex(f->mutex); + return NULL; + } + int idx = f->windex - 1; + if (idx < 0) { + idx = f->max_size - 1; + } + SDL_UnlockMutex(f->mutex); + return &f->queue[idx]; +} + Frame *frame_queue_peek_readable(FrameQueue *f) { - /* wait until we have a readable a new frame */ SDL_LockMutex(f->mutex); while (f->size - f->rindex_shown <= 0 && !f->pktq->abort_request) { @@ -105,14 +152,32 @@ Frame *frame_queue_peek_readable(FrameQueue *f) return &f->queue[(f->rindex + f->rindex_shown) % f->max_size]; } -void frame_queue_push(FrameQueue *f) +Frame *frame_queue_peek_readable_noblock(FrameQueue *f) +{ + SDL_LockMutex(f->mutex); + if (f->size - f->rindex_shown <= 0 && + !f->pktq->abort_request) { + SDL_UnlockMutex(f->mutex); + return NULL; + } + SDL_UnlockMutex(f->mutex); + + if (f->pktq->abort_request) + return NULL; + + return &f->queue[(f->rindex + f->rindex_shown) % f->max_size]; +} + +int frame_queue_push(FrameQueue *f) { if (++f->windex == f->max_size) f->windex = 0; + int size; SDL_LockMutex(f->mutex); - f->size++; + size = ++f->size; SDL_CondSignal(f->cond); SDL_UnlockMutex(f->mutex); + return size; } void frame_queue_next(FrameQueue *f) diff --git a/ijkmedia/ijkplayer/ff_frame_queue.h b/ijkmedia/ijkplayer/ff_frame_queue.h index 8d24f82b5c..740e9cdbef 100644 --- a/ijkmedia/ijkplayer/ff_frame_queue.h +++ b/ijkmedia/ijkplayer/ff_frame_queue.h @@ -16,10 +16,16 @@ void frame_queue_destory(FrameQueue *f); void frame_queue_signal(FrameQueue *f); Frame *frame_queue_peek(FrameQueue *f); Frame *frame_queue_peek_next(FrameQueue *f); +Frame *frame_queue_peek_offset(FrameQueue *f, int offset); Frame *frame_queue_peek_last(FrameQueue *f); +Frame *frame_queue_peek_pre_writable(FrameQueue *f); Frame *frame_queue_peek_writable(FrameQueue *f); +Frame *frame_queue_peek_writable_noblock(FrameQueue *f); +// wait until we have a readable a new frame Frame *frame_queue_peek_readable(FrameQueue *f); -void frame_queue_push(FrameQueue *f); +//return a readable frame or NULL, not wait +Frame *frame_queue_peek_readable_noblock(FrameQueue *f); +int frame_queue_push(FrameQueue *f); int frame_queue_nb_remaining(FrameQueue *f); void frame_queue_next(FrameQueue *f); diff --git a/ijkmedia/ijkplayer/ff_sub_component.c b/ijkmedia/ijkplayer/ff_sub_component.c index 08ed864b58..c6a0c15150 100644 --- a/ijkmedia/ijkplayer/ff_sub_component.c +++ b/ijkmedia/ijkplayer/ff_sub_component.c @@ -8,122 +8,216 @@ #include "ff_sub_component.h" #include "ff_frame_queue.h" #include "ff_packet_list.h" +#include "ff_ass_renderer.h" +#include "ijksdl/ijksdl_gpu.h" +#include "ff_subtitle_def_internal.h" + +#define SUB_MAX_KEEP_DU 3.0 +#define ASS_USE_PRE_RENDER 1 +#define CACHE_ASS_IMG_COUNT 6 +#define A_ASS_IMG_DURATION 0.03 typedef struct FFSubComponent{ int st_idx; PacketQueue* packetq; Decoder decoder; FrameQueue* frameq; - AVFormatContext *ic; - int64_t seek_req; - AVPacket *pkt; - int eof; subComponent_retry_callback retry_callback; void *retry_opaque; + FF_ASS_Renderer *assRenderer; + int bitmapRenderer; + int video_width, video_height; + int sub_width, sub_height; + FFSubtitleBufferPacket sub_buffer_array; + IJKSDLSubtitlePreference sp; + int sp_changed; + float current_pts; + float pre_load_pts; }FFSubComponent; -static int stream_has_enough_packets(PacketQueue *queue, int min_frames) +static void apply_preference(FFSubComponent *com) { - return queue->abort_request || queue->nb_packets > min_frames; + if (com->assRenderer) { + int b = com->sp.bottomMargin * com->sub_height; + com->assRenderer->iformat->update_bottom_margin(com->assRenderer, b); + com->assRenderer->iformat->set_font_scale(com->assRenderer, com->sp.scale); + com->sp_changed = 0; + } } -static int read_packets(FFSubComponent *sub) +#if ASS_USE_PRE_RENDER +static int pre_render_ass_frame(FFSubComponent *com, int serial) { - if (!sub) { + if (com->bitmapRenderer) { return -1; } - if (sub->eof) { - return -2; + if (com->pre_load_pts < 0) { + if (com->current_pts >= 0) { + com->pre_load_pts = com->current_pts; + } } - if (sub->ic) { - sub->pkt->flags = 0; - do { - if (stream_has_enough_packets(sub->packetq, 5)) { - return 1; - } - int ret = av_read_frame(sub->ic, sub->pkt); - if (ret >= 0) { - if (sub->pkt->stream_index != sub->st_idx) { - av_packet_unref(sub->pkt); - continue; - } - packet_queue_put(sub->packetq, sub->pkt); - continue; - } else if (ret == AVERROR_EOF) { - packet_queue_put_nullpacket(sub->packetq, sub->pkt, sub->st_idx); - sub->eof = 1; - return 1; + if (com->sp_changed) { + while (frame_queue_nb_remaining(com->frameq) > 0) { + Frame *af = frame_queue_peek_readable(com->frameq); + if (af) { + frame_queue_next(com->frameq); } else { - return -3; + break; } - } while (sub->packetq->abort_request == 0); + } + if (com->current_pts >= 0) { + //let the pts can display right now. + com->pre_load_pts = com->current_pts - A_ASS_IMG_DURATION; + } + apply_preference(com); } - return -4; -} - -static int get_packet(FFSubComponent *sub, Decoder *d) -{ - while (sub->packetq->abort_request == 0) { + + if (com->pre_load_pts < 0) { + return -1; + } + + if (frame_queue_nb_remaining(com->frameq) >= CACHE_ASS_IMG_COUNT) { + return -1; + } + + FFSubtitleBuffer *pre_buffer = NULL; + FF_ASS_Renderer *assRenderer = ff_ass_render_retain(com->assRenderer); + int result = 0; + while (com->packetq->abort_request == 0) { + FFSubtitleBuffer *buffer = NULL; + double pts = com->pre_load_pts; - if (sub->seek_req >= 0) { - av_log(NULL, AV_LOG_DEBUG,"sub seek to:%lld\n",fftime_to_seconds(sub->seek_req)); - if (avformat_seek_file(sub->ic, -1, INT64_MIN, sub->seek_req, INT64_MAX, 0) < 0) { - av_log(NULL, AV_LOG_WARNING, "%d: could not seek to position %lld\n", - sub->st_idx, sub->seek_req); - sub->seek_req = -1; - return -2; + int r = ff_ass_upload_buffer(com->assRenderer, pts, &buffer, 0); + if (r == 0) { + //no change, reuse pre frame + if (!pre_buffer) { + Frame *lastFrame = frame_queue_peek_pre_writable(com->frameq); + if (lastFrame) { + pre_buffer = ff_subtitle_buffer_retain(lastFrame->sub_list[0]); + } + if (!pre_buffer) { + ff_ass_upload_buffer(com->assRenderer, pts, &pre_buffer, 1); + if (pre_buffer) { + av_log(NULL, AV_LOG_DEBUG, "pre frame is nil, uploaded from ass render.\n"); + } + } } - sub->seek_req = -1; - packet_queue_flush(sub->packetq); + buffer = ff_subtitle_buffer_retain(pre_buffer); + } else if (r > 0) { + // buffer is new. + if (pre_buffer) { + ff_subtitle_buffer_release(&pre_buffer); + } + pre_buffer = ff_subtitle_buffer_retain(buffer); + } else { + //clean + com->pre_load_pts += A_ASS_IMG_DURATION; + result = -1; + break; + } + if (!buffer) { + com->pre_load_pts += A_ASS_IMG_DURATION; + result = -1; + break; + } + + float delta = com->current_pts - pts; + if (delta > A_ASS_IMG_DURATION) { + ff_subtitle_buffer_release(&buffer); + //subtitle is slower than video, so need fast forward + com->pre_load_pts = com->current_pts + 0.2; + av_log(NULL, AV_LOG_WARNING, "subtitle is slower than video:%fs",delta); continue; } - int r = packet_queue_get(d->queue, d->pkt, 0, &d->pkt_serial); - if (r < 0) { - return -1; - } else if (r == 0) { - if (read_packets(sub) >= 0) { - continue; - } else { - av_usleep(1000 * 3); - } + Frame *sp = frame_queue_peek_writable_noblock(com->frameq); + if (!sp) { + ff_subtitle_buffer_release(&buffer); + result = -2; + break; + } + com->pre_load_pts += A_ASS_IMG_DURATION; + sp->pts = pts; + sp->duration = A_ASS_IMG_DURATION; + sp->serial = serial; + sp->width = com->sub_width; + sp->height = com->sub_height; + sp->shown = 0; + sp->sub_list[0] = buffer; + + int size = frame_queue_push(com->frameq); + + if (size > CACHE_ASS_IMG_COUNT) { + break; } else { - return 0; + continue; } } - return -3; + ff_subtitle_buffer_release(&pre_buffer); + ff_ass_render_release(&assRenderer); + return result; } +#endif -static int decode_a_frame(FFSubComponent *sub, Decoder *d, AVSubtitle *pkt) +static int decode_a_frame(FFSubComponent *com, Decoder *d, AVSubtitle *pkt) { int ret = AVERROR(EAGAIN); - for (;sub->packetq->abort_request == 0;) { + for (;com->packetq->abort_request == 0;) { - do { - if (d->packet_pending) { - d->packet_pending = 0; - } else { - int old_serial = d->pkt_serial; - if (get_packet(sub, d) < 0) - return -1; - if (old_serial != d->pkt_serial) { - avcodec_flush_buffers(d->avctx); - d->finished = 0; - d->next_pts = d->start_pts; - d->next_pts_tb = d->start_pts_tb; + if (d->packet_pending) { + d->packet_pending = 0; + } else { + int old_serial = d->pkt_serial; + int get_pkt = packet_queue_get(d->queue, d->pkt, 0, &d->pkt_serial); + //av_log(NULL, AV_LOG_ERROR, "sub packet_queue_get:%d\n",get_pkt); + if (get_pkt < 0) + return -1; + if (get_pkt == 0) { + int r = 1; +#if ASS_USE_PRE_RENDER + r = pre_render_ass_frame(com, old_serial); +#endif + if (r) { + av_usleep(1000 * 10); } + continue; } - if (d->queue->serial == d->pkt_serial) - break; + + if (d->pkt->stream_index != com->st_idx) { + av_packet_unref(d->pkt); + continue; + } + if (old_serial != d->pkt_serial) { + avcodec_flush_buffers(d->avctx); + d->finished = 0; + d->next_pts = d->start_pts; + d->next_pts_tb = d->start_pts_tb; + ff_ass_flush_events(com->assRenderer); + while (frame_queue_nb_remaining(com->frameq) > 0) { + Frame *af = frame_queue_peek_readable(com->frameq); + if (af && af->serial != d->pkt_serial) { + frame_queue_next(com->frameq); + } else { + break; + } + } + com->pre_load_pts = -1; + com->current_pts = -1; + av_log(NULL, AV_LOG_INFO, "sub flush serial:%d\n",d->pkt_serial); + } + } + if (d->queue->serial != d->pkt_serial) + { av_packet_unref(d->pkt); - } while (sub->packetq->abort_request == 0); + continue; + } int got_frame = 0; - //av_log(NULL, AV_LOG_DEBUG, "sub stream decoder pkt serial:%d\n",d->pkt_serial); + //av_log(NULL, AV_LOG_ERROR, "sub stream decoder pkt serial:%d,pts:%lld\n",d->pkt_serial,pkt->pts/1000); ret = avcodec_decode_subtitle2(d->avctx, pkt, &got_frame, d->pkt); if (ret >= 0) { if (got_frame && !d->pkt->data) { @@ -145,22 +239,97 @@ static int decode_a_frame(FFSubComponent *sub, Decoder *d, AVSubtitle *pkt) return -2; } +static void convert_pal_bgra(uint32_t *colors, size_t count, bool gray) +{ + for (int n = 0; n < count; n++) { + uint32_t c = colors[n]; + uint32_t b = c & 0xFF; + uint32_t g = (c >> 8) & 0xFF; + uint32_t r = (c >> 16) & 0xFF; + uint32_t a = (c >> 24) & 0xFF; + if (gray) + r = g = b = (r + g + b) / 3; + // from straight to pre-multiplied alpha + b = b * a / 255; + g = g * a / 255; + r = r * a / 255; + colors[n] = b | (g << 8) | (r << 16) | (a << 24); + } +} + +/// the graphic subtitles' bitmap with pixel format AV_PIX_FMT_PAL8, +/// https://ffmpeg.org/doxygen/trunk/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5 +/// need to be converted to RGBA32 before use + +static FFSubtitleBuffer* convert_pal8_to_bgra(const AVSubtitleRect* rect) +{ + uint32_t pal[256] = {0}; + memcpy(pal, rect->data[1], rect->nb_colors * 4); + convert_pal_bgra(pal, rect->nb_colors, 0); + + SDL_Rectangle r = (SDL_Rectangle){rect->x, rect->y, rect->w, rect->h, rect->linesize[0]}; + FFSubtitleBuffer *frame = ff_subtitle_buffer_alloc_rgba32(r); + if (!frame) { + return NULL; + } + + for (int y = 0; y < rect->h; y++) { + uint8_t *in = rect->data[0] + y * rect->linesize[0]; + uint32_t *out = (uint32_t *)(frame->data + y * frame->rect.stride); + for (int x = 0; x < rect->w; x++) + *out++ = pal[*in++]; + } + return frame; +} + +static int create_ass_renderer_if_need(FFSubComponent *com) +{ + if (com->assRenderer) { + return 0; + } + + int ratio = 1; +#ifdef __APPLE__ + //文本字幕放大两倍,使得 retina 屏显示清楚 + ratio = 2; + com->sub_width = com->video_width * ratio; + com->sub_height = com->video_height * ratio; +#endif + + com->assRenderer = ff_ass_render_create_default(com->decoder.avctx->subtitle_header, com->decoder.avctx->subtitle_header_size, com->sub_width, com->sub_height, NULL); + + apply_preference(com); + + return NULL == com->assRenderer; +} + +static int create_bitmap_renderer_if_need(FFSubComponent *com) +{ + if (com->bitmapRenderer) { + return 0; + } + com->bitmapRenderer = 1; + com->sub_width = com->decoder.avctx->width; + com->sub_height = com->decoder.avctx->height; + if (!com->sub_width || !com->sub_height) { + com->sub_width = com->video_width; + com->sub_height = com->video_height; + } + return 0; +} + static int subtitle_thread(void *arg) { - FFSubComponent *sub = arg; - Frame *sp; + FFSubComponent *com = arg; int got_subtitle; - double pts; - for (;sub->packetq->abort_request == 0;) { - if (!(sp = frame_queue_peek_writable(sub->frameq))) - return 0; - - got_subtitle = decode_a_frame(sub, &sub->decoder, &sp->sub); + for (;com->packetq->abort_request == 0;) { + AVSubtitle sub; + got_subtitle = decode_a_frame(com, &com->decoder, &sub); if (got_subtitle == -1000) { - if (sub->retry_callback) { - sub->retry_callback(sub->retry_opaque); + if (com->retry_callback) { + com->retry_callback(com->retry_opaque); return -1; } break; @@ -169,121 +338,385 @@ static int subtitle_thread(void *arg) if (got_subtitle < 0) break; - pts = 0; if (got_subtitle) { - if (sp->sub.pts != AV_NOPTS_VALUE) - pts = sp->sub.pts / (double)AV_TIME_BASE; - sp->pts = pts; - //av_log(NULL, AV_LOG_DEBUG,"sub received frame:%f\n",pts); - int serial = sub->decoder.pkt_serial; - if (sub->packetq->serial == serial) { - sp->serial = serial; - sp->width = sub->decoder.avctx->width; - sp->height = sub->decoder.avctx->height; - sp->uploaded = 0; - /* now we can update the picture count */ - frame_queue_push(sub->frameq); + //av_log(NULL, AV_LOG_ERROR,"sub received frame:%f\n",pts); + int serial = com->decoder.pkt_serial; + if (com->packetq->serial == serial) { + + double pts = 0; + if (sub.pts != AV_NOPTS_VALUE) + pts = sub.pts / (double)AV_TIME_BASE; + + int count = 0; + FFSubtitleBuffer* buffers [SUB_REF_MAX_LEN] = { 0 }; + for (int i = 0; i < sub.num_rects; i++) { + AVSubtitleRect *rect = sub.rects[i]; + //AV_CODEC_ID_HDMV_PGS_SUBTITLE + //AV_CODEC_ID_FIRST_SUBTITLE + if (rect->type == SUBTITLE_BITMAP) { + if (rect->w <= 0 || rect->h <= 0) { + continue; + } + if (!create_bitmap_renderer_if_need(com)) { + FFSubtitleBuffer* sb = convert_pal8_to_bgra(rect); + if (sb) { + buffers[count++] = sb; + } else { + break; + } + } + } else { + char *ass_line = rect->ass; + if (!ass_line) + continue; + if (!create_ass_renderer_if_need(com)) { + const float begin = pts + (float)sub.start_display_time / 1000.0; + float end = sub.end_display_time - sub.start_display_time; + ff_ass_process_chunk(com->assRenderer, ass_line, begin * 1000, end); + count++; + } + } + } + + if (count == 0) { + avsubtitle_free(&sub); + continue; + } + + if (com->bitmapRenderer) { + Frame *sp = frame_queue_peek_writable(com->frameq); + if (com->packetq->abort_request || !sp) { + avsubtitle_free(&sub); + break; + } + sp->pts = pts + (float)sub.start_display_time / 1000.0; + if (sub.end_display_time > sub.start_display_time && + sub.end_display_time != UINT32_MAX) { + sp->duration = (float)(sub.end_display_time - sub.start_display_time) / 1000.0; + } else { + sp->duration = -1; + Frame *pre = frame_queue_peek_pre_writable(com->frameq); + if (pre) { + pre->duration = sp->pts - pre->pts; + } + } + sp->serial = serial; + sp->width = com->sub_width; + sp->height = com->sub_height; + sp->shown = 0; + + if (count > 0) { + memcpy(sp->sub_list, buffers, count * sizeof(buffers[0])); + } else { + bzero(sp->sub_list, sizeof(sp->sub_list)); + } + frame_queue_push(com->frameq); + } } else { - av_log(NULL, AV_LOG_DEBUG,"sub stream push old frame:%d\n",serial); + //av_log(NULL, AV_LOG_DEBUG,"sub stream push old frame:%d\n",serial); } + avsubtitle_free(&sub); } } + + ff_ass_render_release(&com->assRenderer); + com->retry_callback = NULL; + com->retry_opaque = NULL; + com->st_idx = -1; + return 0; } -int subComponent_open(FFSubComponent **subp, int stream_index, AVFormatContext* ic, AVCodecContext *avctx, PacketQueue* packetq, FrameQueue* frameq, subComponent_retry_callback callback, void *opaque) +static int subComponent_packet_from_frame_queue(FFSubComponent *com, float pts, FFSubtitleBufferPacket *packet, int ignore_cache) { - if (!subp) { + if (!com || !packet) { return -1; } - FFSubComponent *sub = av_mallocz(sizeof(FFSubComponent)); - if (!sub) { + int serial = com->packetq->serial; + + if (serial == -1) { return -2; } + int i = 0; + + while (packet->len < SUB_REF_MAX_LEN) { + Frame *sp = frame_queue_peek_offset(com->frameq, i); + if (!sp) { + break; + } + + //drop old serial subs + if (sp->serial != serial) { + av_log(NULL, AV_LOG_ERROR,"sub stream drop old serial frame:%d\n",sp->serial); + frame_queue_next(com->frameq); + continue; + } + + if (sp->duration > 0) { + if (pts > sp->pts + sp->duration) { + av_log(NULL, AV_LOG_ERROR,"sub stream drop overtime1 frame:%f\n",sp->pts); + frame_queue_next(com->frameq); + continue; + } + } else { + Frame *next = frame_queue_peek_offset(com->frameq, i + 1); + if (next) { + float du = next->pts - sp->pts; + if (du <= 0) { + av_log(NULL, AV_LOG_ERROR,"sub stream drop overtime2 frame:%f\n",sp->pts); + frame_queue_next(com->frameq); + continue; + } else if (du > 0) { + du = du < SUB_MAX_KEEP_DU ? du : SUB_MAX_KEEP_DU; + sp->duration = du; + } + } else { + float delta = pts - sp->pts; + if (delta > SUB_MAX_KEEP_DU) { + av_log(NULL, AV_LOG_ERROR,"sub stream drop overtime3 frame:%f\n",sp->pts); + frame_queue_next(com->frameq); + continue; + } + } + } + + if (!sp->sub_list[0]) { + i++; + continue; + } + + //已经开始 + if (pts > sp->pts) { + for (int j = 0; j < sizeof(sp->sub_list)/sizeof(sp->sub_list[0]); j++) { + FFSubtitleBuffer *sb = sp->sub_list[j]; + if (sb) { + packet->e[packet->len++] = ff_subtitle_buffer_retain(sb); + } else { + break; + } + } + i++; + continue; + } else { + //还没已经开始 + i++; + break; + } + } + + if (packet->len == 0) { + //i > 0 means the frame queue is not empty + return i > 0 ? -1 : -100; + } + + if (ignore_cache || isFFSubtitleBufferArrayDiff(&com->sub_buffer_array, packet)) { + ResetSubtitleBufferArray(&com->sub_buffer_array, packet); + return 1; + } else { + FreeSubtitleBufferArray(packet); + return 0; + } +} + +#if ! ASS_USE_PRE_RENDER +static int subComponent_packet_from_ass_render(FFSubComponent *com, float pts, FFSubtitleBufferPacket *packet) +{ + if (!com || !packet) { + return -1; + } + + FFSubtitleBuffer *sb = NULL; + FF_ASS_Renderer *assRenderer = ff_ass_render_retain(com->assRenderer); + int r = ff_ass_upload_buffer(com->assRenderer, pts, &sb, 0); + ff_ass_render_release(&assRenderer); + if (r > 0) { + packet->e[packet->len++] = sb; + } + return r; +} +#endif + +static int subComponent_packet_ass_from_frame_queue(FFSubComponent *com, float pts, FFSubtitleBufferPacket *packet) +{ + if (com->sp_changed) { + return -100; + } + return subComponent_packet_from_frame_queue(com, pts, packet, 0); +} + +static int subComponent_packet_for_ass(FFSubComponent *com, float pts, FFSubtitleBufferPacket *packet) +{ +#if ASS_USE_PRE_RENDER + return subComponent_packet_ass_from_frame_queue(com, pts, packet); +#else + return subComponent_packet_from_ass_render(com, pts, packet); +#endif +} + +int subComponent_upload_buffer(FFSubComponent *com, float pts, FFSubtitleBufferPacket *packet) +{ + if (!com || com->packetq->abort_request || !packet) { + return -1; + } + + com->current_pts = pts; + + if (com->assRenderer) { + FFSubtitleBufferPacket myPacket = {0}; + myPacket.scale = 1.0; + myPacket.width = com->sub_width; + myPacket.height = com->sub_height; + myPacket.isAss = 1; + + int r = subComponent_packet_for_ass(com, pts, &myPacket); + if (r > 0) { + *packet = myPacket; + } + return r; + } else if (com->bitmapRenderer) { + FFSubtitleBufferPacket myPacket = { 0 }; + myPacket.scale = com->sp.scale; + myPacket.width = com->sub_width; + myPacket.height = com->sub_height; + myPacket.bottom_margin = com->sp.bottomMargin * com->sub_height; + myPacket.isAss = 0; + + int r = subComponent_packet_from_frame_queue(com, pts, &myPacket, com->sp_changed); + if (r > 0) { + com->sp_changed = 0; + *packet = myPacket; + } + return r; + } else { + return -2; + } +} + +int subComponent_open(FFSubComponent **cp, int stream_index, AVStream* stream, PacketQueue* packetq, FrameQueue* frameq, const char *enc, subComponent_retry_callback callback, void *opaque, int vw, int vh) +{ assert(frameq); assert(packetq); - sub->frameq = frameq; - sub->packetq = packetq; - sub->seek_req = -1; - sub->ic = ic; - sub->pkt = av_packet_alloc(); - sub->eof = 0; - sub->retry_callback = callback; - sub->retry_opaque = opaque; + FFSubComponent *com = NULL; + + if (!cp) { + return -2; + } + + if (AVMEDIA_TYPE_SUBTITLE != stream->codecpar->codec_type) { + return -3; + } + + stream->discard = AVDISCARD_DEFAULT; + AVCodecContext* avctx = avcodec_alloc_context3(NULL); + if (!avctx) { + return -4; + } + + if (avcodec_parameters_to_context(avctx, stream->codecpar) < 0) { + return -5; + } + + //so important,ohterwise,sub frame has not pts. + avctx->pkt_timebase = stream->time_base; + if (enc) { + avctx->sub_charenc = av_strdup(enc); + avctx->sub_charenc_mode = FF_SUB_CHARENC_MODE_AUTOMATIC; + } + const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id); + + if (!codec) { + av_log(NULL, AV_LOG_ERROR, "can't find [%s] subtitle decoder!",avcodec_get_name(stream->codecpar->codec_id)); + return -100; + } + + if (avcodec_open2(avctx, codec, NULL) < 0) { + av_log(NULL, AV_LOG_ERROR, "can't open [%s] subtitle decoder!",avcodec_get_name(stream->codecpar->codec_id)); + return -6; + } + + com = av_mallocz(sizeof(FFSubComponent)); + if (!com) { + avcodec_free_context(&avctx); + return -7; + } + + com->video_width = vw; + com->video_height = vh; + com->frameq = frameq; + com->packetq = packetq; + com->retry_callback = callback; + com->retry_opaque = opaque; + com->st_idx = stream_index; + com->sp = ijk_subtitle_default_preference(); + com->current_pts = -1; + com->pre_load_pts = -1; - int ret = decoder_init(&sub->decoder, avctx, sub->packetq, NULL); + int ret = decoder_init(&com->decoder, avctx, com->packetq, NULL); if (ret < 0) { - av_free(sub); - return ret; + av_log(NULL, AV_LOG_ERROR, "subtitle decoder init failed:%d", ret); + avcodec_free_context(&avctx); + av_free(com); + return -8; } - ret = decoder_start(&sub->decoder, subtitle_thread, sub, "ff_subtitle_dec"); + ret = decoder_start(&com->decoder, subtitle_thread, com, "ff_subtitle_dec"); if (ret < 0) { - decoder_destroy(&sub->decoder); - av_free(sub); - return ret; + av_log(NULL, AV_LOG_ERROR, "subtitle decoder start failed:%d", ret); + decoder_destroy(&com->decoder); + av_free(com); + return -9; } - sub->st_idx = stream_index; - av_log(NULL, AV_LOG_DEBUG, "sub stream opened:%d,serial:%d\n", stream_index, packetq->serial); - *subp = sub; + + av_log(NULL, AV_LOG_INFO, "sub stream opened:%d use enc:%s,serial:%d,decoder:%s\n", stream_index, enc, packetq->serial, avcodec_get_name(stream->codecpar->codec_id)); + *cp = com; return 0; } -int subComponent_close(FFSubComponent **subp) +int subComponent_close(FFSubComponent **cp) { - if (!subp) { + if (!cp) { return -1; } - FFSubComponent *sub = *subp; - if (!sub) { + FFSubComponent *com = *cp; + if (!com) { return -2; } - if (sub->st_idx == -1) { + if (com->st_idx == -1) { return -3; } - sub->retry_callback = NULL; - sub->retry_opaque = NULL; - decoder_abort(&sub->decoder, sub->frameq); - decoder_destroy(&sub->decoder); - av_packet_free(&sub->pkt); - av_log(NULL, AV_LOG_DEBUG, "sub stream closed:%d\n", sub->st_idx); - sub->st_idx = -1; - av_freep(subp); + decoder_abort(&com->decoder, com->frameq); + decoder_destroy(&com->decoder); + FreeSubtitleBufferArray(&com->sub_buffer_array); + av_freep(cp); return 0; } -int subComponent_get_stream(FFSubComponent *sub) +int subComponent_get_stream(FFSubComponent *com) { - if (sub) { - return sub->st_idx; + if (com) { + return com->st_idx; } return -1; } -int subComponent_seek_to(FFSubComponent *sub, int sec) +AVCodecContext * subComponent_get_avctx(FFSubComponent *com) { - if (!sub || !sub->ic) { - return -1; - } - if (sec < 0) { - sec = 0; - } - sub->seek_req = seconds_to_fftime(sec); - sub->eof = 0; - return 0; + return com ? com->decoder.avctx : NULL; } -AVCodecContext * subComponent_get_avctx(FFSubComponent *sub) +void subComponent_update_preference(FFSubComponent *com, IJKSDLSubtitlePreference* sp) { - return sub ? sub->decoder.avctx : NULL; -} - -int subComponent_get_serial(FFSubComponent *sub) -{ - return sub ? sub->packetq->serial : -1; + if (!com) { + return; + } + + if (!isIJKSDLSubtitlePreferenceEqual(&com->sp, sp)) { + com->sp = *sp; + com->sp_changed = 1; + } } diff --git a/ijkmedia/ijkplayer/ff_sub_component.h b/ijkmedia/ijkplayer/ff_sub_component.h index 7a6a5790c2..d1cb5fce25 100644 --- a/ijkmedia/ijkplayer/ff_sub_component.h +++ b/ijkmedia/ijkplayer/ff_sub_component.h @@ -10,19 +10,21 @@ #include +typedef void (*subComponent_retry_callback)(void *opaque); + typedef struct FFSubComponent FFSubComponent; typedef struct AVStream AVStream; typedef struct AVCodecContext AVCodecContext; typedef struct PacketQueue PacketQueue; typedef struct FrameQueue FrameQueue; -typedef struct AVFormatContext AVFormatContext; -typedef void (*subComponent_retry_callback)(void *opaque); - +typedef struct IJKSDLSubtitlePreference IJKSDLSubtitlePreference; +typedef struct FFSubtitleBufferPacket FFSubtitleBufferPacket; //when hasn't ic, not support seek; -int subComponent_open(FFSubComponent **subp, int stream_index, AVFormatContext* ic, AVCodecContext *avctx, PacketQueue* packetq, FrameQueue* frameq, subComponent_retry_callback callback, void *opaque); -int subComponent_close(FFSubComponent **subp); -int subComponent_get_stream(FFSubComponent *sub); -int subComponent_seek_to(FFSubComponent *sub, int sec); -AVCodecContext * subComponent_get_avctx(FFSubComponent *sub); -int subComponent_get_serial(FFSubComponent *sub); +int subComponent_open(FFSubComponent **cp, int stream_index, AVStream* stream, PacketQueue* packetq, FrameQueue* frameq, const char *enc, subComponent_retry_callback callback, void *opaque, int vw, int vh); +int subComponent_close(FFSubComponent **cp); +int subComponent_get_stream(FFSubComponent *com); +AVCodecContext * subComponent_get_avctx(FFSubComponent *com); +int subComponent_upload_buffer(FFSubComponent *com, float pts, FFSubtitleBufferPacket *buffer_array); +void subComponent_update_preference(FFSubComponent *com, IJKSDLSubtitlePreference* sp); + #endif /* ff_sub_component_h */ diff --git a/ijkmedia/ijkplayer/ff_subtitle.c b/ijkmedia/ijkplayer/ff_subtitle.c index 17dcb74124..46c664b7d0 100644 --- a/ijkmedia/ijkplayer/ff_subtitle.c +++ b/ijkmedia/ijkplayer/ff_subtitle.c @@ -8,77 +8,119 @@ #include "ff_subtitle.h" #include "ff_frame_queue.h" #include "ff_packet_list.h" -#include "ff_ass_parser.h" #include "ff_subtitle_ex.h" #include "ff_sub_component.h" #include "ff_ffplay_debug.h" +#include "ijksdl_gpu.h" +#include "ijksdl/ijksdl_gpu.h" +#include "ff_subtitle_def_internal.h" + +#define IJK_SUBTITLE_STREAM_UNDEF -2 +#define IJK_SUBTITLE_STREAM_NONE -1 + +#define IJK_EX_SUBTITLE_STREAM_MAX_COUNT 512 +#define IJK_EX_SUBTITLE_STREAM_MIN_OFFSET 1000 +#define IJK_EX_SUBTITLE_STREAM_MAX_OFFSET (IJK_EX_SUBTITLE_STREAM_MIN_OFFSET + IJK_EX_SUBTITLE_STREAM_MAX_COUNT) + +static const char * ff_sub_backup_charenc[] = {"GBK","BIG5-2003"};//没有使用GB18030,否则会把BIG5编码显示成乱码 +static const int ff_sub_backup_charenc_len = 2; typedef struct FFSubtitle { + SDL_mutex* mutex; + int need_update_stream; + int last_stream; + int need_update_preference; + + FFSubComponent* com; + PacketQueue packetq; + PacketQueue packetq2; FrameQueue frameq; float delay; float current_pts; - int maxInternalStream; - FFSubComponent* inSub; - IJKEXSubtitle* exSub; + AVFormatContext* ic_internal; + int maxStream_internal; int streamStartTime;//ic start_time (s) + + FFExSubtitle* exSub; + char* pathArr[IJK_EX_SUBTITLE_STREAM_MAX_COUNT]; + int next_idx; + + int video_w, video_h; + IJKSDLSubtitlePreference sp; + SDL_TextureOverlay *assTexture; + SDL_FBOOverlay *fbo; + SDL_TextureOverlay *preTexture; + + //当前使用的哪个备选字符 + int backup_charenc_idx; + SDL_Thread tmp_retry_thread; }FFSubtitle; -//---------------------------Private Functions--------------------------------------------------// - -static double get_frame_real_begin_pts(FFSubtitle *sub, Frame *sp) -{ - return sp->pts + (float)sp->sub.start_display_time / 1000.0; -} - -static double get_frame_begin_pts(FFSubtitle *sub, Frame *sp) -{ - return sp->pts + (float)sp->sub.start_display_time / 1000.0 + (sub ? sub->delay : 0.0); -} - -static double get_frame_end_pts(FFSubtitle *sub, Frame *sp) -{ - if (sp->sub.end_display_time != 4294967295) { - return sp->pts + (float)sp->sub.end_display_time / 1000.0 + (sub ? sub->delay : 0.0); - } else { - return sp->pts + 5 + (sub ? sub->delay : 0.0); - } -} - -static int stream_has_enough_packets(PacketQueue *queue, int min_frames) -{ - return queue->abort_request || queue->nb_packets > min_frames; -} - //---------------------------Public Common Functions--------------------------------------------------// int ff_sub_init(FFSubtitle **subp) { + int r = 0; if (!subp) { return -1; } FFSubtitle *sub = av_mallocz(sizeof(FFSubtitle)); if (!sub) { - return -2; + r = -2; + goto fail; + } + + sub->mutex = SDL_CreateMutex(); + + if (NULL == sub->mutex) { + r = -3; + goto fail; } if (packet_queue_init(&sub->packetq) < 0) { - av_free(sub); - return -3; + r = -4; + goto fail; + } + + if (packet_queue_init(&sub->packetq2) < 0) { + r = -4; + goto fail; } + packet_queue_start(&sub->packetq2); if (frame_queue_init(&sub->frameq, &sub->packetq, SUBPICTURE_QUEUE_SIZE, 0) < 0) { packet_queue_destroy(&sub->packetq); - av_free(sub); - return -4; + r = -5; + goto fail; } + sub->delay = 0.0f; sub->current_pts = 0.0f; - sub->maxInternalStream = -1; - + sub->maxStream_internal = -1; + sub->need_update_stream = IJK_SUBTITLE_STREAM_UNDEF; + sub->last_stream = IJK_SUBTITLE_STREAM_UNDEF; + sub->need_update_preference = 0; + sub->sp = ijk_subtitle_default_preference(); *subp = sub; + return 0; +fail: + if (sub && sub->mutex) { + SDL_DestroyMutex(sub->mutex); + } + if (sub) { + av_free(sub); + } + return r; +} + +void ff_sub_desctoy_objs(FFSubtitle *sub) +{ + SDL_TextureOverlay_Release(&sub->assTexture); + SDL_TextureOverlay_Release(&sub->preTexture); + SDL_FBOOverlayFreeP(&sub->fbo); } void ff_sub_abort(FFSubtitle *sub) @@ -99,118 +141,438 @@ int ff_sub_destroy(FFSubtitle **subp) if (!sub) { return -2; } - if (sub->inSub) { - subComponent_close(&sub->inSub); - } else if (exSub_get_opened_stream_idx(sub->exSub) != -1) { - exSub_subtitle_destroy(&sub->exSub); + + SDL_LockMutex(sub->mutex); + if (sub->com) { + subComponent_close(&sub->com); + } + if (sub->exSub) { + exSub_close_input(&sub->exSub); } + SDL_UnlockMutex(sub->mutex); + packet_queue_destroy(&sub->packetq); + packet_queue_destroy(&sub->packetq2); frame_queue_destory(&sub->frameq); sub->delay = 0.0f; sub->current_pts = 0.0f; - sub->maxInternalStream = -1; + sub->maxStream_internal = -1; + + SDL_LockMutex(sub->mutex); + for (int i = 0; i < sub->next_idx; i++) { + if (sub->pathArr[i]) { + av_free(sub->pathArr[i]); + } + } + SDL_UnlockMutex(sub->mutex); + SDL_DestroyMutex(sub->mutex); av_freep(subp); return 0; } -int ff_sub_close_current(FFSubtitle *sub) +int ff_sub_drop_old_frames(FFSubtitle *sub) { - if (sub->inSub) { - return subComponent_close(&sub->inSub); - } else if (exSub_get_opened_stream_idx(sub->exSub) != -1) { - return exSub_close_current(sub->exSub); + int count = 0; + int serial = sub->packetq.serial; + while (frame_queue_nb_remaining(&sub->frameq) > 0) { + Frame *sp = frame_queue_peek(&sub->frameq); + if (sp->serial != serial) { + frame_queue_next(&sub->frameq); + count++; + continue; + } else { + break; + } } - return -1; + return count; } -static void ff_sub_clean_frame_queue(FFSubtitle *sub) +static int ff_sub_upload_buffer(FFSubtitle *sub, float pts, FFSubtitleBufferPacket *packet) { - if (!sub) { - return; + if (!sub || !packet) { + return -1; } - FrameQueue *subpq = &sub->frameq; - while (frame_queue_nb_remaining(subpq) > 0) { - frame_queue_next(subpq); + SDL_LockMutex(sub->mutex); + sub->current_pts = pts; + pts += (sub ? sub->delay : 0.0); + int err = -2; + if (sub->com) { + if (subComponent_get_stream(sub->com) >= 0) { + err = subComponent_upload_buffer(sub->com, pts, packet); + } } + SDL_UnlockMutex(sub->mutex); + return err; } -int ff_sub_fetch_frame(FFSubtitle *sub, float pts, char **text, AVSubtitleRect **bmp) +static SDL_TextureOverlay * subtitle_ass_upload_texture(SDL_TextureOverlay *texture, FFSubtitleBufferPacket *packet) { - if (!sub) { + texture->clearDirtyRect(texture); + for (int i = 0; i < packet->len; i++) { + FFSubtitleBuffer *buffer = packet->e[i]; + texture->replaceRegion(texture, buffer->rect, buffer->data); + } + return SDL_TextureOverlay_Retain(texture); +} + +static SDL_TextureOverlay * subtitle_upload_fbo(SDL_GPU *gpu, SDL_FBOOverlay *fbo, FFSubtitleBufferPacket *packet) +{ + fbo->beginDraw(gpu, fbo, 0); + fbo->clear(fbo); + int water_mark = fbo->h * SUBTITLE_MOVE_WATERMARK; + int bottom_offset = packet->bottom_margin; + for (int i = 0; i < packet->len; i++) { + FFSubtitleBuffer *sub = packet->e[i]; + + SDL_TextureOverlay *texture = gpu->createTexture(gpu, sub->rect.w, sub->rect.h, SDL_TEXTURE_FMT_BRGA); + texture->scale = packet->scale; + + SDL_Rectangle bounds = sub->rect; + bounds.x = 0; + bounds.y = 0; + texture->replaceRegion(texture, bounds, sub->data); + + int offset = sub->rect.y > water_mark ? bottom_offset : 0; + SDL_Rectangle frame = sub->rect; + frame.y -= offset; + + fbo->drawTexture(gpu, fbo, texture, frame); + SDL_TextureOverlay_Release(&texture); + } + fbo->endDraw(gpu, fbo); + return fbo->getTexture(fbo); +} + +//if *texture is not NULL, it was retained +static int ff_sub_upload_texture(FFSubtitle *sub, float pts, SDL_GPU *gpu, SDL_TextureOverlay **texture) +{ + if (!sub || !texture) { return -1; } - int serial = -1; - - if (sub->inSub) { - if (subComponent_get_stream(sub->inSub) != -1) { - serial = subComponent_get_serial(sub->inSub); - } + FFSubtitleBufferPacket packet = {0}; + int r = ff_sub_upload_buffer(sub, pts, &packet); + if (r <= 0) { + *texture = NULL; + return r; } - if (serial == -1 && sub->exSub) { - if (exSub_get_opened_stream_idx(sub->exSub) != -1) { - serial = exSub_get_serial(sub->exSub); - pts -= sub->streamStartTime; + if (packet.isAss) { + if (sub->assTexture && (sub->assTexture->w != packet.width || sub->assTexture->h != packet.height)) { + SDL_TextureOverlay_Release(&sub->assTexture); + } + if (!sub->assTexture) { + sub->assTexture = gpu->createTexture(gpu, packet.width, packet.height, SDL_TEXTURE_FMT_BRGA); } + if (!sub->assTexture) { + r = -1; + goto end; + } + + *texture = subtitle_ass_upload_texture(sub->assTexture, &packet); + } else { + if (sub->fbo && (sub->fbo->w != packet.width || sub->fbo->h != packet.height)) { + SDL_FBOOverlayFreeP(&sub->fbo); + } + if (!sub->fbo) { + sub->fbo = gpu->createFBO(gpu, packet.width, packet.height); + } + if (!sub->fbo) { + r = -1; + goto end; + } + + *texture = subtitle_upload_fbo(gpu, sub->fbo, &packet); + } +end: + FreeSubtitleBufferArray(&packet); + return r; +} + +int ff_sub_get_texture(FFSubtitle *sub, float pts, SDL_GPU *gpu, SDL_TextureOverlay **texture) +{ + if (!texture) { + return -1; } - if (serial == -1) { - return -2; + SDL_TextureOverlay *sub_overlay = NULL; + int r = ff_sub_upload_texture(sub, pts, gpu, &sub_overlay); + if (r > 0) { + //replace + SDL_TextureOverlay_Release(&sub->preTexture); + sub->preTexture = sub_overlay; + } else if (r < 0) { + //clean + SDL_TextureOverlay_Release(&sub->preTexture); + } else { + //keep current } - int r = 1; - int rem = 0; - int dropped = 0; - while ((rem = frame_queue_nb_remaining(&sub->frameq)) > 0) { - if (rem > 1) { - Frame *sp2 = frame_queue_peek_next(&sub->frameq); - if (pts > get_frame_begin_pts(sub, sp2)) { - dropped++; - frame_queue_next(&sub->frameq); - continue; + *texture = SDL_TextureOverlay_Retain(sub->preTexture); + return r; +} + +void ff_sub_stream_ic_ready(FFSubtitle *sub, AVFormatContext* ic, int video_w, int video_h) +{ + if (!sub) { + return; + } + sub->video_w = video_w; + sub->video_h = video_h; + sub->streamStartTime = (int)fftime_to_seconds(ic->start_time); + sub->maxStream_internal = ic->nb_streams; + sub->ic_internal = ic; +} + +int ff_sub_is_need_update_stream(FFSubtitle *sub) +{ + int r; + SDL_LockMutex(sub->mutex); + r = sub->need_update_stream != IJK_SUBTITLE_STREAM_UNDEF; + SDL_UnlockMutex(sub->mutex); + return r; +} + +//when close current stream "st_idx" is -1 +int ff_sub_record_need_select_stream(FFSubtitle *sub, int st_idx) +{ + int r; + SDL_LockMutex(sub->mutex); + if (sub->last_stream == st_idx) { + r = 0; + } else { + sub->need_update_stream = st_idx; + r = 1; + } + SDL_UnlockMutex(sub->mutex); + return r; +} + +int ff_sub_is_need_update_preference(FFSubtitle *sub) +{ + int r; + SDL_LockMutex(sub->mutex); + r = sub->need_update_preference; + SDL_UnlockMutex(sub->mutex); + return r; +} + +static int convert_ext_idx_to_fileIdx(int idx) +{ + int arr_idx = -1; + if (idx >= IJK_EX_SUBTITLE_STREAM_MIN_OFFSET && idx < IJK_EX_SUBTITLE_STREAM_MAX_OFFSET) { + arr_idx = (idx - IJK_EX_SUBTITLE_STREAM_MIN_OFFSET) % IJK_EX_SUBTITLE_STREAM_MAX_COUNT; + } + return arr_idx; +} + +static const char * ext_file_path_for_idx(FFSubtitle *sub, int idx) +{ + int arr_idx = convert_ext_idx_to_fileIdx(idx); + if (arr_idx != -1) { + return sub->pathArr[arr_idx]; + } + return NULL; +} + +static int do_retry_next_charenc(void *opaque); + +static void retry_callback(void *opaque) +{ + FFSubtitle *sub = opaque; + if (!sub) { + return; + } + //fix "Use of deallocated memory" crash + //in other thread close this ex subtitle stream is necessory:because when destroy decoder,will join this thread,but join self won't join anything,then freed SDL_Thread struct,and func return value can't assign to retval! (thread->retval = thread->func(thread->data);) + //if you want reproduce the crash,may need open "Address Sanitizer" option + SDL_CreateThreadEx(&sub->tmp_retry_thread, do_retry_next_charenc, opaque, "tmp_retry"); +} + +static void move_backup_to_normal(FFSubtitle *sub, int stream) +{ + AVPacket pkt; + while (1) { + int get_pkt = packet_queue_get(&sub->packetq2, &pkt, 0, NULL); + if (get_pkt > 0) { + if (pkt.stream_index == stream) { + av_log(NULL, AV_LOG_INFO,"sub move backup to normal:%d,%lld\n", pkt.stream_index, pkt.pts/1000); + packet_queue_put(&sub->packetq, &pkt); + } else { + av_packet_unref(&pkt); } - } - Frame * sp = frame_queue_peek(&sub->frameq); - if (sp->serial != serial) { - dropped++; - frame_queue_next(&sub->frameq); continue; } - sub->current_pts = get_frame_real_begin_pts(sub, sp); - float begin = sub->current_pts + (sub ? sub->delay : 0.0); - float end = get_frame_end_pts(sub, sp); - - if (pts > begin && pts < end) { - if (!sp->uploaded) { - if (sp->sub.num_rects > 0) { - if (sp->sub.rects[0]->text) { - *text = av_strdup(sp->sub.rects[0]->text); - } else if (sp->sub.rects[0]->ass) { - *text = parse_ass_subtitle(sp->sub.rects[0]->ass); - } else if (sp->sub.rects[0]->type == SUBTITLE_BITMAP - && sp->sub.rects[0]->data[0] - && sp->sub.rects[0]->linesize[0]) { - *bmp = sp->sub.rects[0]; - } else { - assert(0); - } + break; + } +} + +static int open_any_stream(FFSubtitle *sub, int stream, const char *enc) +{ + if (stream < 0) { + return -2; + } + if (stream < sub->ic_internal->nb_streams) { + //open internal + AVStream *st = sub->ic_internal->streams[sub->need_update_stream]; + int r = subComponent_open(&sub->com, stream, st, &sub->packetq, &sub->frameq, enc, &retry_callback, (void *)sub, sub->video_w, sub->video_h); + if (!r) { + subComponent_update_preference(sub->com, &sub->sp); + move_backup_to_normal(sub, stream); + } + return r; + } else { + const char *file = ext_file_path_for_idx(sub, stream); + if (file) { + if (!exSub_open_input(&sub->exSub, &sub->packetq, file)) { + AVStream *st = exSub_get_stream(sub->exSub); + int st_id = exSub_get_stream_id(sub->exSub); + int r = subComponent_open(&sub->com, st_id, st, &sub->packetq, &sub->frameq, enc, &retry_callback, (void *)sub, sub->video_w, sub->video_h); + if (!r) { + subComponent_update_preference(sub->com, &sub->sp); + exSub_start_read(sub->exSub); + return 0; + } else { + exSub_close_input(&sub->exSub); + return r; } - r = 0; - sp->uploaded = 1; + } else { + return -2; } - //av_log(NULL, AV_LOG_ERROR, "sub fetch frame :%0.2f,%0.2f,%0.2f;dropped:%d\n",pts,sub->current_pts,(sub ? sub->delay : 0.0),dropped); } else { - //av_log(NULL, AV_LOG_ERROR, "sub fetch frame failed:%0.2f,%0.2f,%0.2f;dropped:%d\n",pts,sub->current_pts,(sub ? sub->delay : 0.0),dropped); - if (sp->uploaded) { - sp->uploaded = 0; + return -3; + } + } +} + +static int do_retry_next_charenc(void *opaque) +{ + FFSubtitle *sub = opaque; + if (!sub) { + return -1; + } + + SDL_LockMutex(sub->mutex); + + int st_id = sub->last_stream; + if (st_id < 0) { + SDL_UnlockMutex(sub->mutex); + return -2; + } + + if (sub->backup_charenc_idx >= ff_sub_backup_charenc_len) { + SDL_UnlockMutex(sub->mutex); + return -3; + } + + const char *enc = ff_sub_backup_charenc[sub->backup_charenc_idx]; + sub->backup_charenc_idx++; + //close old + packet_queue_abort(&sub->packetq); + if (sub->exSub) { + exSub_close_input(&sub->exSub); + } + subComponent_close(&sub->com); + //open new + open_any_stream(sub, st_id, enc); + SDL_UnlockMutex(sub->mutex); + return 0; +} + +static int ff_sub_close_current(FFSubtitle *sub) +{ + if (!sub) { + return -1; + } + sub->last_stream = IJK_SUBTITLE_STREAM_UNDEF; + + int r = 0; + + ff_sub_abort(sub); + + if (sub->exSub) { + exSub_close_input(&sub->exSub); + } + + if (sub->com) { + r = subComponent_close(&sub->com); + } + + return r; +} + +//-1: no change. 0:close current. 1:opened new, less than -1 means open failed +int ff_sub_update_stream_if_need(FFSubtitle *sub, int *update_stream) +{ + int r = -1; + SDL_LockMutex(sub->mutex); + if (sub->need_update_stream != IJK_SUBTITLE_STREAM_UNDEF) { + //close current + if (sub->last_stream != IJK_SUBTITLE_STREAM_UNDEF) { + //close + ff_sub_close_current(sub); + sub->last_stream = IJK_SUBTITLE_STREAM_UNDEF; + r = 0; + } + + if (update_stream) { + *update_stream = sub->need_update_stream; + } + //open new + if (sub->need_update_stream != IJK_SUBTITLE_STREAM_NONE) { + //reset to 0 + sub->backup_charenc_idx = 0; + int err = open_any_stream(sub, sub->need_update_stream, NULL); + if (err) { + r = err; + } else { + sub->last_stream = sub->need_update_stream; + r = 1; } - //clean current display sub - r = -3; } - break; + sub->need_update_stream = IJK_SUBTITLE_STREAM_UNDEF; + } + SDL_UnlockMutex(sub->mutex); + return r; +} + +AVCodecContext * ff_sub_get_avctx(FFSubtitle *sub) +{ + if (!sub || !sub->com) { + return NULL; + } + + return subComponent_get_avctx(sub->com); +} + +int ff_sub_get_current_stream(FFSubtitle *sub, int *pending) +{ + int r; + SDL_LockMutex(sub->mutex); + r = sub->last_stream; + if (pending) { + *pending = sub->need_update_stream; + } + SDL_UnlockMutex(sub->mutex); + return r; +} + +//0 means has no sub;1 means internal sub;2 means external sub; +int ff_sub_current_stream_type(FFSubtitle *sub) +{ + int r = 0; + if (sub) { + SDL_LockMutex(sub->mutex); + if (sub->last_stream < 0) { + r = 0; + } else if (sub->last_stream < sub->ic_internal->nb_streams) { + r = 1; + } else if (sub->exSub) { + r = 2; + } + SDL_UnlockMutex(sub->mutex); } return r; } @@ -226,7 +588,7 @@ int ff_sub_frame_queue_size(FFSubtitle *sub) int ff_sub_has_enough_packets(FFSubtitle *sub, int min_frames) { if (sub) { - return stream_has_enough_packets(&sub->packetq, min_frames); + return sub->packetq.abort_request || sub->packetq.nb_packets > min_frames; } return 1; } @@ -242,34 +604,31 @@ int ff_sub_put_null_packet(FFSubtitle *sub, AVPacket *pkt, int st_idx) int ff_sub_put_packet(FFSubtitle *sub, AVPacket *pkt) { if (sub) { + move_backup_to_normal(sub, pkt->stream_index); + //av_log(NULL, AV_LOG_INFO,"sub put pkt:%lld\n",pkt->pts/1000); return packet_queue_put(&sub->packetq, pkt); } return -1; } -int ff_sub_get_opened_stream_idx(FFSubtitle *sub) +int ff_sub_put_packet_backup(FFSubtitle *sub, AVPacket *pkt) { - int idx = -1; if (sub) { - if (sub->inSub) { - idx = subComponent_get_stream(sub->inSub); - } - if (idx == -1 && sub->exSub) { - idx = exSub_get_opened_stream_idx(sub->exSub); - } + //av_log(NULL, AV_LOG_INFO,"sub put pkt to backup:%lld\n",pkt->pts/1000); + return packet_queue_put(&sub->packetq2, pkt); } - return idx; + return -1; } void ff_sub_seek_to(FFSubtitle *sub, float delay, float v_pts) { - ff_sub_clean_frame_queue(sub); - if (exSub_get_opened_stream_idx(sub->exSub) != -1) { + if (ff_sub_current_stream_type(sub) == 2) { v_pts -= sub->streamStartTime; + float wantDisplay = v_pts - delay; + SDL_LockMutex(sub->mutex); + exSub_seek_to(sub->exSub, wantDisplay); + SDL_UnlockMutex(sub->mutex); } - - float wantDisplay = v_pts - delay; - exSub_seek_to(sub->exSub, wantDisplay); } int ff_sub_set_delay(FFSubtitle *sub, float delay, float v_pts) @@ -277,9 +636,8 @@ int ff_sub_set_delay(FFSubtitle *sub, float delay, float v_pts) if (!sub) { return -1; } - if (exSub_get_opened_stream_idx(sub->exSub) != -1) { - v_pts -= sub->streamStartTime; - } + + v_pts -= sub->streamStartTime; float wantDisplay = v_pts - delay; //subtile's frame queue greater than can display pts @@ -287,17 +645,18 @@ int ff_sub_set_delay(FFSubtitle *sub, float delay, float v_pts) float diff = fabsf(delay - sub->delay); sub->delay = delay; //need seek to wantDisplay; - if (sub->inSub) { + int type = ff_sub_current_stream_type(sub); + if (type == 1) { //after seek maybe can display want sub,but can't seek every dealy change,so when diff greater than 2s do seek. if (diff > 2) { - ff_sub_clean_frame_queue(sub); //return 1 means need seek. return 1; } return -2; - } else if (exSub_get_opened_stream_idx(sub->exSub) != -1) { - ff_sub_clean_frame_queue(sub); + } else if (type == 2) { + SDL_LockMutex(sub->mutex); exSub_seek_to(sub->exSub, wantDisplay-2); + SDL_UnlockMutex(sub->mutex); return 0; } else { return -3; @@ -314,138 +673,91 @@ float ff_sub_get_delay(FFSubtitle *sub) return sub ? sub->delay : 0.0; } -int ff_sub_isInternal_stream(FFSubtitle *sub, int stream) +int ff_sub_packet_queue_flush(FFSubtitle *sub) { - if (!sub) { - return 0; - } - return stream >= 0 && stream <= sub->maxInternalStream; -} - -int ff_sub_isExternal_stream(FFSubtitle *sub, int stream) -{ - if (!sub) { + if (sub) { + packet_queue_flush(&sub->packetq); + packet_queue_flush(&sub->packetq2); return 0; } - return exSub_contain_streamIdx(sub->exSub, stream); + return -1; } -int ff_sub_current_stream_type(FFSubtitle *sub, int *outIdx) +int ff_update_sub_preference(FFSubtitle *sub, IJKSDLSubtitlePreference* sp) { - int type = 0; - int idx = -1; + int r = 0; if (sub) { - if (sub->inSub) { - idx = subComponent_get_stream(sub->inSub); - type = 1; - } - if (idx == -1 && sub->exSub) { - idx = exSub_get_opened_stream_idx(sub->exSub); - if (idx != -1) { - type = 2; - } + SDL_LockMutex(sub->mutex); + sub->sp = *sp; + if (sub->com) { + subComponent_update_preference(sub->com, sp); + r = 1; } + SDL_UnlockMutex(sub->mutex); } - - if (outIdx) { - *outIdx = idx; - } - return type; + return r; } -void ff_sub_stream_ic_ready(FFSubtitle *sub,AVFormatContext* ic) +//---------------------------External Subtitle Functions--------------------------------------------------// +static void create_meta(IjkMediaMeta **out_meta, int idx, const char *url) { - sub->streamStartTime = (int)fftime_to_seconds(ic->start_time); - sub->maxInternalStream = ic->nb_streams; -} + if (!out_meta) + return; -int ff_sub_open_component(FFSubtitle *sub, int stream_index, AVFormatContext* ic, AVCodecContext *avctx) -{ - if (sub->inSub || sub->exSub) { - packet_queue_flush(&sub->packetq); - ff_sub_clean_frame_queue(sub); - } - return subComponent_open(&sub->inSub, stream_index, ic, avctx, &sub->packetq, &sub->frameq, NULL, NULL); + IjkMediaMeta *stream_meta = ijkmeta_create(); + if (!stream_meta) + return; + + int stream_idx = idx + IJK_EX_SUBTITLE_STREAM_MIN_OFFSET; + ijkmeta_set_int64_l(stream_meta, IJKM_KEY_STREAM_IDX, stream_idx); + ijkmeta_set_string_l(stream_meta, IJKM_KEY_TYPE, IJKM_VAL_TYPE__TIMEDTEXT); + ijkmeta_set_string_l(stream_meta, IJKM_KEY_EX_SUBTITLE_URL, url); + char title[16] = {0}; + snprintf(title, 16, "Track%d", idx + 1); + ijkmeta_set_string_l(stream_meta, IJKM_KEY_TITLE, title); + + *out_meta = stream_meta; } -enum AVCodecID ff_sub_get_codec_id(FFSubtitle *sub) +int ff_sub_add_ex_subtitle(FFSubtitle *sub, const char *file_name, IjkMediaMeta **out_meta, int *out_idx) { if (!sub) { return -1; } - AVCodecContext *avctx = NULL; - int idx = -1; - if (sub->inSub) { - idx = subComponent_get_stream(sub->inSub); - if (idx != -1) { - avctx = subComponent_get_avctx(sub->inSub); - } - } - if (idx == -1 && sub->exSub) { - idx = exSub_get_opened_stream_idx(sub->exSub); - if (idx != -1) { - avctx = exSub_get_avctx(sub->exSub); - } - } - - return avctx ? avctx->codec_id : AV_CODEC_ID_NONE; -} -int ff_inSub_packet_queue_flush(FFSubtitle *sub) -{ - if (sub) { - if (sub->inSub) { - packet_queue_flush(&sub->packetq); - } - return 0; - } - return -1; -} - -//---------------------------Internal Subtitle Functions--------------------------------------------------// - -// - -//---------------------------External Subtitle Functions--------------------------------------------------// - -int ff_exSub_addOnly_subtitle(FFSubtitle *sub, const char *file_name, IjkMediaMeta *meta) -{ - if (!sub->exSub) { - if (exSub_create(&sub->exSub, &sub->frameq, &sub->packetq) != 0) { - return -1; + int already_added = 0; + //maybe already added. + SDL_LockMutex(sub->mutex); + for (int i = 0; i < sub->next_idx; i++) { + char* next = sub->pathArr[i]; + if (next && (0 == av_strcasecmp(next, file_name))) { + already_added = 1; + break; } } + SDL_UnlockMutex(sub->mutex); - return exSub_addOnly_subtitle(sub->exSub, file_name, meta); -} - -int ff_exSub_add_active_subtitle(FFSubtitle *sub, const char *file_name, IjkMediaMeta *meta) -{ - if (!sub->exSub) { - if (exSub_create(&sub->exSub, &sub->frameq, &sub->packetq) != 0) { - return -1; + if (already_added) { + if (out_idx) { + *out_idx = -1; } + return 1; } - packet_queue_flush(&sub->packetq); - ff_sub_clean_frame_queue(sub); - return exSub_add_active_subtitle(sub->exSub, file_name, meta); -} - -int ff_exSub_open_stream(FFSubtitle *sub, int stream) -{ - if (!sub->exSub) { - return -1; - } - packet_queue_flush(&sub->packetq); - ff_sub_clean_frame_queue(sub); - return exSub_open_file_idx(sub->exSub, stream); -} - -int ff_exSub_check_file_added(const char *file_name, FFSubtitle *sub) -{ - if (!sub || !sub->exSub) { - return -1; + int r; + SDL_LockMutex(sub->mutex); + if (sub->next_idx < IJK_EX_SUBTITLE_STREAM_MAX_COUNT) { + int idx = sub->next_idx; + sub->pathArr[idx] = av_strdup(file_name); + sub->next_idx++; + create_meta(out_meta, idx, sub->pathArr[idx]); + if (out_idx) { + *out_idx = idx + IJK_EX_SUBTITLE_STREAM_MIN_OFFSET; + } + r = 0; + } else { + r = -2; } - return exSub_check_file_added(file_name, sub->exSub); + SDL_UnlockMutex(sub->mutex); + return r; } diff --git a/ijkmedia/ijkplayer/ff_subtitle.h b/ijkmedia/ijkplayer/ff_subtitle.h index 08d74324ad..000ad8c246 100644 --- a/ijkmedia/ijkplayer/ff_subtitle.h +++ b/ijkmedia/ijkplayer/ff_subtitle.h @@ -8,53 +8,55 @@ #ifndef ff_subtitle_h #define ff_subtitle_h -#include +#include "ff_subtitle_def.h" typedef struct FFSubtitle FFSubtitle; -typedef struct AVSubtitleRect AVSubtitleRect; -typedef struct AVStream AVStream; typedef struct AVCodecContext AVCodecContext; typedef struct AVPacket AVPacket; typedef struct IjkMediaMeta IjkMediaMeta; typedef struct AVFormatContext AVFormatContext; +typedef struct SDL_TextureOverlay SDL_TextureOverlay; +typedef struct SDL_GPU SDL_GPU; + // lifecycle int ff_sub_init(FFSubtitle **subp); +//call in vout thread,because internal fbo and texture were created in vout thread! +void ff_sub_desctoy_objs(FFSubtitle *sub); void ff_sub_abort(FFSubtitle *sub); int ff_sub_destroy(FFSubtitle **subp); -// -int ff_sub_open_component(FFSubtitle *sub, int stream_index, AVFormatContext* ic, AVCodecContext *avctx); -int ff_sub_close_current(FFSubtitle *sub); -//return zero means out has content -int ff_sub_fetch_frame(FFSubtitle *sub, float pts, char **text, AVSubtitleRect **bmp); +//when video steam ic ready,call me. +void ff_sub_stream_ic_ready(FFSubtitle *sub, AVFormatContext* ic, int video_w, int video_h); +int ff_sub_is_need_update_stream(FFSubtitle *sub); +int ff_sub_record_need_select_stream(FFSubtitle *sub, int st_idx); +int ff_sub_is_need_update_preference(FFSubtitle *sub); +//-1: no change. 0:close current. 1:opened new, less than -1 means open failed +int ff_sub_update_stream_if_need(FFSubtitle *sub, int *update_stream); +AVCodecContext * ff_sub_get_avctx(FFSubtitle *sub); +//less than 0 means none opened stream,pending is will use stream id +int ff_sub_get_current_stream(FFSubtitle *sub, int *pending); +//0 means has no sub;1 means internal sub;2 means external sub; +int ff_sub_current_stream_type(FFSubtitle *sub); + +int ff_sub_get_texture(FFSubtitle *sub, float pts, SDL_GPU *gpu, SDL_TextureOverlay **texture); +int ff_sub_drop_old_frames(FFSubtitle *sub); int ff_sub_frame_queue_size(FFSubtitle *sub); int ff_sub_has_enough_packets(FFSubtitle *sub, int min_frames); - int ff_sub_put_null_packet(FFSubtitle *sub, AVPacket *pkt, int st_idx); - int ff_sub_put_packet(FFSubtitle *sub, AVPacket *pkt); +int ff_sub_put_packet_backup(FFSubtitle *sub, AVPacket *pkt); +int ff_sub_packet_queue_flush(FFSubtitle *sub); -int ff_sub_get_opened_stream_idx(FFSubtitle *sub); -void ff_sub_seek_to(FFSubtitle *sub, float delay, float v_pts); int ff_sub_set_delay(FFSubtitle *sub, float delay, float cp); float ff_sub_get_delay(FFSubtitle *sub); -enum AVCodecID ff_sub_get_codec_id(FFSubtitle *sub); - -int ff_inSub_packet_queue_flush(FFSubtitle *sub); -// return 0 means not internal,but not means is external; -int ff_sub_isInternal_stream(FFSubtitle *sub, int stream); -// return 0 means not external,but not means is internal; -int ff_sub_isExternal_stream(FFSubtitle *sub, int stream); -//0 means has no sub;1 means internal sub;2 means external sub; -int ff_sub_current_stream_type(FFSubtitle *sub, int *outIdx); -//when video steam ic ready,call me. -void ff_sub_stream_ic_ready(FFSubtitle *sub,AVFormatContext* ic); +//return 1 means need refresh display +int ff_update_sub_preference(FFSubtitle *sub, IJKSDLSubtitlePreference* sp); //for external subtitle. -int ff_exSub_addOnly_subtitle(FFSubtitle *sub, const char *file_name, IjkMediaMeta *meta); -int ff_exSub_add_active_subtitle(FFSubtitle *sub, const char *file_name, IjkMediaMeta *meta); -int ff_exSub_open_stream(FFSubtitle *sub, int stream); -int ff_exSub_check_file_added(const char *file_name, FFSubtitle *ffSub); +int ff_sub_add_ex_subtitle(FFSubtitle *sub, const char *file_name, IjkMediaMeta **out_meta, int *out_idx); +//only effect on external subtitle +void ff_sub_seek_to(FFSubtitle *sub, float delay, float v_pts); + #endif /* ff_subtitle_h */ diff --git a/ijkmedia/ijkplayer/ff_subtitle_def.c b/ijkmedia/ijkplayer/ff_subtitle_def.c new file mode 100644 index 0000000000..d7d8ba4a70 --- /dev/null +++ b/ijkmedia/ijkplayer/ff_subtitle_def.c @@ -0,0 +1,109 @@ +// +// ff_subtitle_def.c +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/3/5. +// + +#include "ff_subtitle_def_internal.h" +#include +#include + +FFSubtitleBuffer *ff_subtitle_buffer_alloc_rgba32(SDL_Rectangle rect) +{ + if (rect.stride == 0) { + rect.stride = rect.w * 4; + } else { + rect.stride *= 4; + } + + FFSubtitleBuffer *img = malloc(sizeof(FFSubtitleBuffer)); + bzero(img, sizeof(FFSubtitleBuffer)); + img->rect = rect; + size_t size = rect.h * rect.stride; + img->data = calloc(1, size); + memset(img->data, 0, size); + img->refCount = 1; + return img; +} + +FFSubtitleBuffer * ff_subtitle_buffer_retain(FFSubtitleBuffer *sb) +{ + if (sb) { + __atomic_add_fetch(&sb->refCount, 1, __ATOMIC_RELEASE); + } + return sb; +} + +void ff_subtitle_buffer_release(FFSubtitleBuffer **sbp) +{ + if (sbp) { + FFSubtitleBuffer *sb = *sbp; + if (sb) { + if (__atomic_add_fetch(&sb->refCount, -1, __ATOMIC_RELEASE) == 0) { + free(sb->data); + free(sb); + } + *sbp = NULL; + } + } +} + +int isFFSubtitleBufferArrayDiff(FFSubtitleBufferPacket *a1, FFSubtitleBufferPacket *a2) +{ + if (a1 == a2) { + return 0; + } + + if (!a1 || !a2) { + return 1; + } + + if (a1->len != a2->len) { + return 1; + } + + for (int i = 0; i < a1->len; i++) { + FFSubtitleBuffer *h1 = a1->e[i]; + FFSubtitleBuffer *h2 = a2->e[i]; + if (h1 != h2) { + return 1; + } else if (h1 == NULL) { + return 0; + } else { + continue; + } + } + return 0; +} + +void FreeSubtitleBufferArray(FFSubtitleBufferPacket *a) +{ + if (a) { + while (a->len > 0) { + ff_subtitle_buffer_release(&a->e[--a->len]); + } + } +} + +void ResetSubtitleBufferArray(FFSubtitleBufferPacket *dst, FFSubtitleBufferPacket *src) +{ + if (!dst) { + return; + } + FreeSubtitleBufferArray(dst); + + if (!src) { + return; + } + + int i = 0; + while (dst->len <= SUB_REF_MAX_LEN && i < src->len) { + dst->e[dst->len++] = ff_subtitle_buffer_retain(src->e[i++]); + } + dst->scale = src->scale; + dst->bottom_margin = src->bottom_margin; + dst->width = src->width; + dst->height = src->height; + dst->isAss = src->isAss; +} diff --git a/ijkmedia/ijkplayer/ff_subtitle_def.h b/ijkmedia/ijkplayer/ff_subtitle_def.h new file mode 100644 index 0000000000..0d3e19d502 --- /dev/null +++ b/ijkmedia/ijkplayer/ff_subtitle_def.h @@ -0,0 +1,74 @@ +// +// ff_subtitle_def.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/3/5. +// + +#ifndef ff_subtitle_def_h +#define ff_subtitle_def_h + +#include +#include +#include "ijksdl_rectangle.h" + +typedef struct FFSubtitleBuffer { + SDL_Rectangle rect; + unsigned char *data; + int refCount; +} FFSubtitleBuffer; + +FFSubtitleBuffer * ff_subtitle_buffer_retain(FFSubtitleBuffer *); +void ff_subtitle_buffer_release(FFSubtitleBuffer **); + +typedef struct IJKSDLSubtitlePreference { + char name[256];//font name + float scale; //font scale,default 1.0 + uint32_t color;//text color + uint32_t bgColor;//text bg color + uint32_t strokeColor;//border color + int strokeSize;//stroke size + float bottomMargin;//[0.0,1.0] +} IJKSDLSubtitlePreference; + +static inline IJKSDLSubtitlePreference ijk_subtitle_default_preference(void) +{ + return (IJKSDLSubtitlePreference){"", 1.0, 4294967295, 0, 255, 5, 0.025}; +} + +static inline int isIJKSDLSubtitlePreferenceEqual(IJKSDLSubtitlePreference* p1,IJKSDLSubtitlePreference* p2) +{ + if (!p1 || !p2) { + return 0; + } + if (p1->scale != p2->scale || + p1->color != p2->color || + p1->bgColor != p2->bgColor || + p1->strokeColor != p2->strokeColor || + p1->strokeSize != p2->strokeSize || + p1->bottomMargin != p2->bottomMargin || + strcmp(p1->name, p2->name) + ) { + return 0; + } + return 1; +} + +#define SUB_REF_MAX_LEN 32 + +typedef struct FFSubtitleBufferPacket { + FFSubtitleBuffer *e[SUB_REF_MAX_LEN]; + int len; + float scale; + int bottom_margin; + int isAss; + int width; + int height; +} FFSubtitleBufferPacket; + +//return zero means equal +int isFFSubtitleBufferArrayDiff(FFSubtitleBufferPacket *a1, FFSubtitleBufferPacket *a2); +void FreeSubtitleBufferArray(FFSubtitleBufferPacket *a); +void ResetSubtitleBufferArray(FFSubtitleBufferPacket *dst, FFSubtitleBufferPacket *src); + +#endif /* ff_subtitle_def_h */ diff --git a/ijkmedia/ijkplayer/ff_subtitle_def_internal.h b/ijkmedia/ijkplayer/ff_subtitle_def_internal.h new file mode 100644 index 0000000000..655657f75f --- /dev/null +++ b/ijkmedia/ijkplayer/ff_subtitle_def_internal.h @@ -0,0 +1,17 @@ +// +// ff_subtitle_def_internal.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/3/28. +// + +#ifndef ff_subtitle_def_internal_hpp +#define ff_subtitle_def_internal_hpp + +#include "ff_subtitle_def.h" +//忽略向上移动的字幕范围 [0-0.8] +#define SUBTITLE_MOVE_WATERMARK 0.8 + +FFSubtitleBuffer *ff_subtitle_buffer_alloc_rgba32(SDL_Rectangle rect); + +#endif /* ff_subtitle_def_internal_hpp */ diff --git a/ijkmedia/ijkplayer/ff_subtitle_ex.c b/ijkmedia/ijkplayer/ff_subtitle_ex.c index ee806d6fca..7979ab78d0 100644 --- a/ijkmedia/ijkplayer/ff_subtitle_ex.c +++ b/ijkmedia/ijkplayer/ff_subtitle_ex.c @@ -7,161 +7,90 @@ // after activate not need seek, because video stream will be seeked. #include "ff_subtitle_ex.h" -#include "libavformat/avformat.h" #include "ff_ffplay_def.h" -#include "ff_frame_queue.h" #include "ff_packet_list.h" -#include "ff_ass_parser.h" -#include "ff_sub_component.h" -#define IJK_EX_SUBTITLE_STREAM_MAX_COUNT 100 -#define IJK_EX_SUBTITLE_STREAM_MIN_OFFSET 1000 -#define IJK_EX_SUBTITLE_STREAM_MAX_OFFSET (IJK_EX_SUBTITLE_STREAM_MIN_OFFSET + IJK_EX_SUBTITLE_STREAM_MAX_COUNT) - -static const char * ff_sub_backup_charenc[] = {"GBK","BIG5-2003"};//没有使用GB18030,否则会把BIG5编码显示成乱码 -static const int ff_sub_backup_charenc_len = 2; - -typedef struct IJKEXSubtitle { - SDL_mutex* mutex; - FFSubComponent* component; +typedef struct FFExSubtitle { AVFormatContext* ic; - int st_offset_idx;//相对于 IJK_EX_SUBTITLE_STREAM_MIN_OFFSET 的 - FrameQueue * frameq; PacketQueue * pktq; - char* pathArr[IJK_EX_SUBTITLE_STREAM_MAX_COUNT]; - int next_idx; - //当前使用的哪个备选字符 - int backup_charenc_idx; - SDL_Thread tmp_retry_thread; -}IJKEXSubtitle; + SDL_Thread _read_thread; + SDL_Thread *read_thread; + int stream_id;//ic 里的 + int eof; + int64_t seek_req; +}FFExSubtitle; -int exSub_create(IJKEXSubtitle **subp, FrameQueue * frameq, PacketQueue * pktq) +static int stream_has_enough_packets(PacketQueue *queue, int min_frames) { - if (!subp) { - return -1; - } - - IJKEXSubtitle *sub = av_malloc(sizeof(IJKEXSubtitle)); - if (!sub) { - return -2; - } - bzero(sub, sizeof(IJKEXSubtitle)); - - sub->mutex = SDL_CreateMutex(); - if (NULL == sub->mutex) { - av_free(sub); - return -2; - } - sub->frameq = frameq; - sub->pktq = pktq; - sub->st_offset_idx = -1; - *subp = sub; - return 0; -} - -int exSub_get_opened_stream_idx(IJKEXSubtitle *sub) -{ - if (sub && sub->component) { - return sub->st_offset_idx; - } - return -1; + return queue->abort_request || queue->nb_packets > min_frames; } -int exSub_seek_to(IJKEXSubtitle *sub, float sec) +static int ex_read_thread(void *opaque) { - if (!sub || !sub->component) { + FFExSubtitle *sub = opaque; + if (!sub) { return -1; } - return subComponent_seek_to(sub->component, sec); -} - -static int convert_idx_from_stream(int idx) -{ - int arr_idx = -1; - if (idx >= IJK_EX_SUBTITLE_STREAM_MIN_OFFSET && idx < IJK_EX_SUBTITLE_STREAM_MAX_OFFSET) { - arr_idx = (idx - IJK_EX_SUBTITLE_STREAM_MIN_OFFSET) % IJK_EX_SUBTITLE_STREAM_MAX_COUNT; - } - return arr_idx; -} - -static int do_retry_next_charenc(void *opaque); - -static void retry_callback(void *opaque) -{ - IJKEXSubtitle *sub = opaque; - if (!sub) { - return; + + AVPacket *pkt = av_packet_alloc(); + pkt->flags = 0; + if (sub->ic) { + do { + if (sub->seek_req >= 0) { + av_log(NULL, AV_LOG_DEBUG,"external subtitle seek to:%lld\n",fftime_to_seconds(sub->seek_req)); + if (avformat_seek_file(sub->ic, -1, INT64_MIN, sub->seek_req, INT64_MAX, 0) < 0) { + av_log(NULL, AV_LOG_ERROR, "external subtitle could not seek to position %lld\n", sub->seek_req); + } + sub->seek_req = -1; + sub->eof = 0; + packet_queue_flush(sub->pktq); + continue; + } + + if (sub->eof) { + av_usleep(3 * 1000); + continue; + } + + if (stream_has_enough_packets(sub->pktq, 16)) { + av_usleep(3 * 1000); + continue; + } + int ret = av_read_frame(sub->ic, pkt); + if (ret >= 0) { + if (pkt->stream_index != sub->stream_id) { + av_packet_unref(pkt); + continue; + } + packet_queue_put(sub->pktq, pkt); + continue; + } else if (ret == AVERROR_EOF) { + packet_queue_put_nullpacket(sub->pktq, pkt, sub->stream_id); + sub->eof = 1; + continue; + } else { + av_usleep(3 * 1000); + continue; + } + } while (sub->pktq->abort_request == 0); } - //fix "Use of deallocated memory" crash - //in other thread close this ex subtitle stream is necessory:because when destroy decoder,will join this thread,but join self won't join anything,then freed SDL_Thread struct,and func return value can't assign to retval! (thread->retval = thread->func(thread->data);) - //if you want reproduce the crash,may need open "Address Sanitizer" option - SDL_CreateThreadEx(&sub->tmp_retry_thread, do_retry_next_charenc, opaque, "tmp_retry"); + av_packet_free(&pkt); + return 0; } -static int do_retry_next_charenc(void *opaque) +int exSub_seek_to(FFExSubtitle *sub, float sec) { - IJKEXSubtitle *sub = opaque; - if (!sub) { + if (!sub || !sub->ic) { return -1; } - - SDL_LockMutex(sub->mutex); - - if (sub->backup_charenc_idx >= ff_sub_backup_charenc_len) { - goto fail; - } - - int stream_idx = subComponent_get_stream(sub->component); - if (stream_idx == -1) { - goto fail; - } - - subComponent_close(&sub->component); - //reopen - if (!sub->ic) { - goto fail; + if (sec < 0) { + sec = 0; } - - AVCodecContext* avctx = avcodec_alloc_context3(NULL); - if (!avctx) { - goto fail; - } - - if (stream_idx >= sub->ic->nb_streams) { - goto fail; - } - - AVStream *sub_st = sub->ic->streams[stream_idx]; - if (avcodec_parameters_to_context(avctx, sub_st->codecpar) < 0) { - goto fail; - } - - //so important,ohterwise,sub frame has not pts. - avctx->pkt_timebase = sub_st->time_base; - const char *enc = ff_sub_backup_charenc[sub->backup_charenc_idx]; - avctx->sub_charenc = av_strdup(enc); - avctx->sub_charenc_mode = FF_SUB_CHARENC_MODE_AUTOMATIC; - sub->backup_charenc_idx++; - - const AVCodec* codec = avcodec_find_decoder(sub_st->codecpar->codec_id); - if (!codec) { - goto fail; - } - - if (avcodec_open2(avctx, codec, NULL) < 0) { - goto fail; - } - - if (subComponent_open(&sub->component, stream_idx, sub->ic, avctx, sub->pktq, sub->frameq, &retry_callback, (void *)sub) != 0) { - goto fail; - } - subComponent_seek_to(sub->component, 0); -fail: - SDL_UnlockMutex(sub->mutex); + sub->seek_req = seconds_to_fftime(sec); return 0; } -static int exSub_open_filepath(IJKEXSubtitle *sub, const char *file_name, int idx) +static int exSub_open_filepath(FFExSubtitle *sub, const char *file_name) { if (!sub) { return -1; @@ -170,10 +99,11 @@ static int exSub_open_filepath(IJKEXSubtitle *sub, const char *file_name, int id if (!file_name || strlen(file_name) == 0) { return -2; } - + + assert(!sub->ic); + int ret = 0; AVFormatContext* ic = NULL; - AVCodecContext* avctx = NULL; if (avformat_open_input(&ic, file_name, NULL, NULL) < 0) { ret = -1; @@ -202,267 +132,94 @@ static int exSub_open_filepath(IJKEXSubtitle *sub, const char *file_name, int id } } - if (!sub_st) { + if (stream_id == -1) { ret = -3; av_log(NULL, AV_LOG_ERROR, "none subtitle stream in %s\n", file_name); goto fail; } - const AVCodec* codec = avcodec_find_decoder(sub_st->codecpar->codec_id); - if (!codec) { - av_log(NULL, AV_LOG_WARNING, "could find codec:%s for %s\n", - file_name, avcodec_get_name(sub_st->codecpar->codec_id)); - ret = -4; - goto fail; - } - - avctx = avcodec_alloc_context3(NULL); - if (!avctx) { - ret = -5; - goto fail; - } - - if (avcodec_parameters_to_context(avctx, sub_st->codecpar) < 0) { - ret = -6; - goto fail; - } - //so important,ohterwise,sub frame has not pts. - avctx->pkt_timebase = sub_st->time_base; - - if (avcodec_open2(avctx, codec, NULL) < 0) { - ret = -7; - goto fail; - } - - if (subComponent_open(&sub->component, stream_id, ic, avctx, sub->pktq, sub->frameq, &retry_callback, (void *)sub) != 0) { - ret = -8; - goto fail; - } - - //reset to 0 - sub->backup_charenc_idx = 0; sub->ic = ic; - sub->st_offset_idx = idx; + sub->stream_id = stream_id; + sub->seek_req = -1; return 0; fail: - if (ret < 0) { - if (ic) - avformat_close_input(&ic); - if (avctx) - avcodec_free_context(&avctx); - } + if (ic) + avformat_close_input(&ic); return ret; } -int exSub_open_file_idx(IJKEXSubtitle *sub, int idx) +static int exSub_create(FFExSubtitle **subp, PacketQueue * pktq) { - if (!sub) { + if (!subp) { return -1; } - if (idx == -1) { + FFExSubtitle *sub = av_malloc(sizeof(FFExSubtitle)); + if (!sub) { return -2; } + bzero(sub, sizeof(FFExSubtitle)); - int arr_idx = convert_idx_from_stream(idx); - - if (idx == -1) { - return -3; - } - - const char *file_name = sub->pathArr[arr_idx]; - - if (!file_name) { - return -4; - } - - if (exSub_open_filepath(sub, file_name, idx) != 0) { - return -5; - } - + sub->pktq = pktq; + *subp = sub; return 0; } -int exSub_close_current(IJKEXSubtitle *sub) -{ - if(!sub) { - return -1; - } - SDL_LockMutex(sub->mutex); - int r = subComponent_close(&sub->component); - if (sub->ic) - avformat_close_input(&sub->ic); - SDL_UnlockMutex(sub->mutex); - return r; -} - -void exSub_subtitle_destroy(IJKEXSubtitle **subp) +int exSub_open_input(FFExSubtitle **subp, PacketQueue * pktq, const char *file_name) { if (!subp) { - return; + return -1; } - IJKEXSubtitle *sub = *subp; - if (!sub) { - return; + FFExSubtitle *mySub = NULL; + if (exSub_create(&mySub, pktq)) { + return -2; } - exSub_close_current(sub); - - SDL_LockMutex(sub->mutex); - for (int i = 0; i < sub->next_idx; i++) { - if (sub->pathArr[i]) { - av_free(sub->pathArr[i]); - } + if (exSub_open_filepath(mySub, file_name) != 0) { + exSub_close_input(&mySub); + return -3; } - SDL_UnlockMutex(sub->mutex); - - SDL_DestroyMutex(sub->mutex); - av_freep(subp); + *subp = mySub; + return 0; } -static void ijkmeta_set_ex_subtitle_context_l(IjkMediaMeta *meta, struct AVFormatContext *ic, IJKEXSubtitle *sub, int actived_stream) +void exSub_start_read(FFExSubtitle *sub) { - if (!meta || !sub) - return; - - if (actived_stream != -1) { - ijkmeta_set_int64_l(meta, IJKM_KEY_TIMEDTEXT_STREAM, actived_stream); - } - - IjkMediaMeta *stream_meta = ijkmeta_create(); - if (!stream_meta) - return; - int idx = sub->next_idx - 1; - char *url = sub->pathArr[idx]; - int stream_idx = idx + IJK_EX_SUBTITLE_STREAM_MIN_OFFSET; - ijkmeta_set_int64_l(stream_meta, IJKM_KEY_STREAM_IDX, stream_idx); - ijkmeta_set_string_l(stream_meta, IJKM_KEY_TYPE, IJKM_VAL_TYPE__TIMEDTEXT); - ijkmeta_set_string_l(stream_meta, IJKM_KEY_EX_SUBTITLE_URL, url); - char title[64] = {0}; - snprintf(title, 64, "Track%d", sub->next_idx); - ijkmeta_set_string_l(stream_meta, IJKM_KEY_TITLE, title); - - ijkmeta_append_child_l(meta, stream_meta); - - if (!ic) { - return; - } - for (int i = 0; i < ic->nb_streams; i++) { - AVStream *st = ic->streams[i]; - if (st && st->codecpar) { - AVCodecParameters *codecpar = st->codecpar; - if (codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { - const char *codec_name = avcodec_get_name(codecpar->codec_id); - if (codec_name) - ijkmeta_set_string_l(stream_meta, IJKM_KEY_CODEC_NAME, codec_name); - - AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0); - if (lang && lang->value) - ijkmeta_set_string_l(stream_meta, IJKM_KEY_LANGUAGE, lang->value); - - AVDictionaryEntry *t = av_dict_get(st->metadata, "title", NULL, 0); - if (t && t->value) { - ijkmeta_set_string_l(stream_meta, IJKM_KEY_TITLE, t->value); - } - break; - } - } - } + sub->read_thread = SDL_CreateThreadEx(&sub->_read_thread, ex_read_thread, sub, "ex_read_thread"); } -int exSub_addOnly_subtitle(IJKEXSubtitle *sub, const char *file_name, IjkMediaMeta *meta) +void exSub_close_input(FFExSubtitle **subp) { - if (!sub) { - return -1; - } - - if (exSub_check_file_added(file_name, sub)) { - return 1; + if (!subp) { + return; } - if (sub->next_idx < IJK_EX_SUBTITLE_STREAM_MAX_COUNT) { - SDL_LockMutex(sub->mutex); - int idx = sub->next_idx; - sub->pathArr[idx] = av_strdup(file_name); - sub->next_idx++; - ijkmeta_set_ex_subtitle_context_l(meta, NULL, sub, -1); - SDL_UnlockMutex(sub->mutex); - } else { - return -2; - } - return 0; -} - -int exSub_check_file_added(const char *file_name, IJKEXSubtitle *sub) -{ - SDL_LockMutex(sub->mutex); - bool already_added = 0; - //maybe already added. - for (int i = 0; i < sub->next_idx; i++) { - char* next = sub->pathArr[i]; - if (next && (0 == av_strcasecmp(next, file_name))) { - already_added = 1; - break; - } - } - SDL_UnlockMutex(sub->mutex); - return already_added; -} - -int exSub_add_active_subtitle(IJKEXSubtitle *sub, const char *file_name, IjkMediaMeta *meta) -{ + FFExSubtitle *sub = *subp; if (!sub) { - return -1; + return; } - - if (exSub_check_file_added(file_name, sub)) { - return 1; + //maybe open input failed, read_thread is NULL. + if (sub->read_thread) { + SDL_WaitThread(sub->read_thread, NULL); + sub->read_thread = NULL; } - int idx = sub->next_idx; - - if (idx < IJK_EX_SUBTITLE_STREAM_MAX_COUNT) { - SDL_LockMutex(sub->mutex); - int r = exSub_open_filepath(sub, file_name, idx + IJK_EX_SUBTITLE_STREAM_MIN_OFFSET); - if (r != 0) { - av_log(NULL, AV_LOG_ERROR, "could not open ex subtitle:(%d)%s\n", r, file_name); - SDL_UnlockMutex(sub->mutex); - return -2; - } - sub->pathArr[idx] = av_strdup(file_name); - sub->next_idx++; - ijkmeta_set_ex_subtitle_context_l(meta, sub->ic, sub, idx + IJK_EX_SUBTITLE_STREAM_MIN_OFFSET); - SDL_UnlockMutex(sub->mutex); - return 0; - } else { - return -3; - } + if (sub->ic) + avformat_close_input(&sub->ic); + av_freep(subp); } -int exSub_contain_streamIdx(IJKEXSubtitle *sub, int idx) +AVStream * exSub_get_stream(FFExSubtitle *sub) { - if (!sub) { - return 0; + if (sub->stream_id < sub->ic->nb_streams) { + return sub->ic->streams[sub->stream_id]; } - - SDL_LockMutex(sub->mutex); - int arr_idx = convert_idx_from_stream(idx); - if (arr_idx < 0 || arr_idx >= sub->next_idx || NULL == sub->pathArr[arr_idx]) { - av_log(NULL, AV_LOG_ERROR, "invalid stream index %d is NULL\n", idx); - arr_idx = -1; - } - SDL_UnlockMutex(sub->mutex); - return arr_idx != -1; -} - -AVCodecContext * exSub_get_avctx(IJKEXSubtitle *sub) -{ - return sub ? subComponent_get_avctx(sub->component) : NULL; + return NULL; } -int exSub_get_serial(IJKEXSubtitle *sub) +int exSub_get_stream_id(FFExSubtitle *sub) { - return sub ? subComponent_get_serial(sub->component) : -1; + return sub->stream_id; } diff --git a/ijkmedia/ijkplayer/ff_subtitle_ex.h b/ijkmedia/ijkplayer/ff_subtitle_ex.h index b47e5f8d3a..2ad5d85fda 100644 --- a/ijkmedia/ijkplayer/ff_subtitle_ex.h +++ b/ijkmedia/ijkplayer/ff_subtitle_ex.h @@ -10,27 +10,16 @@ #include -typedef struct FFSubtitle FFSubtitle; -typedef struct IJKEXSubtitle IJKEXSubtitle; -typedef struct IjkMediaMeta IjkMediaMeta; -typedef struct FrameQueue FrameQueue; +typedef struct FFExSubtitle FFExSubtitle; typedef struct PacketQueue PacketQueue; -typedef struct AVCodecContext AVCodecContext; +typedef struct AVStream AVStream; -int exSub_create(IJKEXSubtitle **subp, FrameQueue * frameq, PacketQueue * pktq); -int exSub_check_file_added(const char *file_name, IJKEXSubtitle *sub); -int exSub_addOnly_subtitle(IJKEXSubtitle *sub, const char *file_name, IjkMediaMeta *meta); -int exSub_add_active_subtitle(IJKEXSubtitle *sub, const char *file_name, IjkMediaMeta *meta); -int exSub_open_file_idx(IJKEXSubtitle *sub, int idx); -int exSub_close_current(IJKEXSubtitle *sub); -void exSub_subtitle_destroy(IJKEXSubtitle **sub); - -//when return -1 means has not opened; -int exSub_get_opened_stream_idx(IJKEXSubtitle *sub); +int exSub_open_input(FFExSubtitle **subp, PacketQueue * pktq, const char *file_name); +void exSub_start_read(FFExSubtitle *sub); +void exSub_close_input(FFExSubtitle **sub); +AVStream * exSub_get_stream(FFExSubtitle *sub); +int exSub_get_stream_id(FFExSubtitle *sub); //when return zero means succ; -int exSub_seek_to(IJKEXSubtitle *sub, float sec); -int exSub_contain_streamIdx(IJKEXSubtitle *sub, int idx); -AVCodecContext * exSub_get_avctx(IJKEXSubtitle *sub); -int exSub_get_serial(IJKEXSubtitle *sub); +int exSub_seek_to(FFExSubtitle *sub, float sec); #endif /* ff_subtitle_ex_h */ diff --git a/ijkmedia/ijkplayer/ijkavformat/ijklas.c b/ijkmedia/ijkplayer/ijkavformat/ijklas.c index a6081d95b3..05ca193a85 100644 --- a/ijkmedia/ijkplayer/ijkavformat/ijklas.c +++ b/ijkmedia/ijkplayer/ijkavformat/ijklas.c @@ -1834,7 +1834,7 @@ static int las_close(AVFormatContext* s) { return 0; } -static int las_probe(AVProbeData* p) { +static int las_probe(const AVProbeData* p) { if (p->filename && strstr(p->filename, ".las")) return AVPROBE_SCORE_MAX; diff --git a/ijkmedia/ijkplayer/ijkavformat/ijklivehook.c b/ijkmedia/ijkplayer/ijkavformat/ijklivehook.c index eb5d5f5be5..ce3f73414b 100644 --- a/ijkmedia/ijkplayer/ijkavformat/ijklivehook.c +++ b/ijkmedia/ijkplayer/ijkavformat/ijklivehook.c @@ -72,7 +72,7 @@ static int ijkurlhook_call_inject(AVFormatContext *h) return ret; } -static int ijklivehook_probe(AVProbeData *probe) +static int ijklivehook_probe(const AVProbeData *probe) { if (av_strstart(probe->filename, "ijklivehook:", NULL)) return AVPROBE_SCORE_MAX; diff --git a/ijkmedia/ijkplayer/ijkmeta.c b/ijkmedia/ijkplayer/ijkmeta.c index 9ddb6d4fd7..c909b71feb 100755 --- a/ijkmedia/ijkplayer/ijkmeta.c +++ b/ijkmedia/ijkplayer/ijkmeta.c @@ -191,18 +191,49 @@ void ijkmeta_set_avformat_context_l(IjkMediaMeta *meta, AVFormatContext *ic) if (ic->bit_rate) ijkmeta_set_int64_l(meta, IJKM_KEY_BITRATE, ic->bit_rate); - - AVDictionaryEntry *artist = av_dict_get(ic->metadata, IJKM_KEY_ARTIST, NULL, 0); - if (artist && artist->value) - ijkmeta_set_string_l(meta, IJKM_KEY_ARTIST, artist->value); - AVDictionaryEntry *album = av_dict_get(ic->metadata, IJKM_KEY_ALBUM, NULL, 0); - if (album && album->value) - ijkmeta_set_string_l(meta, IJKM_KEY_ALBUM, album->value); - AVDictionaryEntry *tyer = av_dict_get(ic->metadata, IJKM_KEY_TYER, NULL, 0); - if (tyer && tyer->value) - ijkmeta_set_string_l(meta, IJKM_KEY_TYER, tyer->value); -// //debug all ic metadata + char *ic_string_val_keys[] = {IJKM_KEY_ARTIST,IJKM_KEY_ALBUM,IJKM_KEY_TYER,IJKM_KEY_MINOR_VER,IJKM_KEY_COMPATIBLE_BRANDS,IJKM_KEY_MAJOR_BRAND,NULL}; + { + char **ic_key_header = ic_string_val_keys; + char *ic_key; + while ((ic_key = *ic_key_header)) { + AVDictionaryEntry *entry = av_dict_get(ic->metadata, ic_key, NULL, 0); + if (entry && entry->value) + ijkmeta_set_string_l(meta, ic_key, entry->value); + ic_key_header++; + } + } + + { + IjkMediaMeta *chapter_meta = NULL; + for (int i = 0; i < ic->nb_chapters; i++) { + if (!chapter_meta) { + chapter_meta = ijkmeta_create(); + } + AVChapter *chapter = ic->chapters[i]; + //ms + double tb = av_q2d(chapter->time_base) * 1000; + long start = (long)(chapter->start * tb); + long end = (long)(chapter->end * tb); + IjkMediaMeta *sub_meta = ijkmeta_create(); + ijkmeta_set_int64_l(sub_meta, IJKM_META_KEY_START, start); + ijkmeta_set_int64_l(sub_meta, IJKM_META_KEY_END, end); + ijkmeta_set_int64_l(sub_meta, IJKM_META_KEY_ID, chapter->id); + + //set all raw meta + AVDictionaryEntry *tag = NULL; + while ((tag = av_dict_get(chapter->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) + ijkmeta_set_string_l(sub_meta, tag->key, tag->value); + + ijkmeta_append_child_l(chapter_meta, sub_meta); + } + if (chapter_meta) { + ijkmeta_set_string_l(chapter_meta, IJKM_KEY_TYPE, IJKM_VAL_TYPE__CHAPTER); + ijkmeta_append_child_l(meta, chapter_meta); + } + } + + //debug all ic metadata // AVDictionaryEntry *tag = NULL; // while ((tag = av_dict_get(ic->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) // printf("ic metadata item:%s=%s\n", tag->key, tag->value); diff --git a/ijkmedia/ijkplayer/ijkmeta.h b/ijkmedia/ijkplayer/ijkmeta.h index 36c8254316..e1f1677aa8 100755 --- a/ijkmedia/ijkplayer/ijkmeta.h +++ b/ijkmedia/ijkplayer/ijkmeta.h @@ -28,26 +28,36 @@ #include // media meta -#define IJKM_KEY_FORMAT "format" -#define IJKM_KEY_DURATION_US "duration_us" -#define IJKM_KEY_START_US "start_us" -#define IJKM_KEY_BITRATE "bitrate" -#define IJKM_KEY_VIDEO_STREAM "video" -#define IJKM_KEY_AUDIO_STREAM "audio" -#define IJKM_KEY_TIMEDTEXT_STREAM "timedtext" +#define IJKM_KEY_FORMAT "format" +#define IJKM_KEY_DURATION_US "duration_us" +#define IJKM_KEY_START_US "start_us" +#define IJKM_KEY_BITRATE "bitrate" +#define IJKM_KEY_VIDEO_STREAM "video" +#define IJKM_KEY_AUDIO_STREAM "audio" +#define IJKM_KEY_TIMEDTEXT_STREAM "timedtext" // stream meta #define IJKM_KEY_TYPE "type" #define IJKM_VAL_TYPE__VIDEO "video" #define IJKM_VAL_TYPE__AUDIO "audio" #define IJKM_VAL_TYPE__TIMEDTEXT "timedtext" +#define IJKM_VAL_TYPE__CHAPTER "chapter" #define IJKM_VAL_TYPE__UNKNOWN "unknown" +#define IJKM_META_KEY_ID "id" +#define IJKM_META_KEY_START "start" +#define IJKM_META_KEY_END "end" +#define IJKM_META_KEY_TITLE "title" + #define IJKM_KEY_LANGUAGE "language" #define IJKM_KEY_TITLE "title" #define IJKM_KEY_STREAM_IDX "stream_idx" #define IJKM_KEY_ARTIST "artist" #define IJKM_KEY_ALBUM "album" #define IJKM_KEY_TYER "TYER" +#define IJKM_KEY_ENCODER "encoder" +#define IJKM_KEY_MINOR_VER "minor_version" +#define IJKM_KEY_COMPATIBLE_BRANDS "compatible_brands" +#define IJKM_KEY_MAJOR_BRAND "major_brand" #define IJKM_KEY_CODEC_NAME "codec_name" #define IJKM_KEY_CODEC_PROFILE "codec_profile" @@ -65,6 +75,7 @@ #define IJKM_KEY_TBR_DEN "tbr_den" #define IJKM_KEY_SAR_NUM "sar_num" #define IJKM_KEY_SAR_DEN "sar_den" + // stream: audio #define IJKM_KEY_SAMPLE_RATE "sample_rate" //#define IJKM_KEY_CHANNEL_LAYOUT "channel_layout" diff --git a/ijkmedia/ijkplayer/ijkplayer.c b/ijkmedia/ijkplayer/ijkplayer.c index ded8be93f9..d9ffdd8828 100755 --- a/ijkmedia/ijkplayer/ijkplayer.c +++ b/ijkmedia/ijkplayer/ijkplayer.c @@ -847,14 +847,20 @@ void ijkmp_set_audio_sample_observer(IjkMediaPlayer *mp, ijk_audio_samples_callb ffp_set_audio_sample_observer(mp->ffplayer, cb); } -void ijk_set_enable_accurate_seek(IjkMediaPlayer *mp,int open) +void ijkmp_set_enable_accurate_seek(IjkMediaPlayer *mp, int open) { assert(mp); ffp_set_enable_accurate_seek(mp->ffplayer, open); } -void ijk_step_to_next_frame(IjkMediaPlayer *mp) +void ijkmp_step_to_next_frame(IjkMediaPlayer *mp) { assert(mp); ffp_step_to_next_frame(mp->ffplayer); } + +void ijkmp_set_subtitle_preference(IjkMediaPlayer *mp, IJKSDLSubtitlePreference* sp) +{ + assert(mp); + ffp_set_subtitle_preference(mp->ffplayer, sp); +} diff --git a/ijkmedia/ijkplayer/ijkplayer.h b/ijkmedia/ijkplayer/ijkplayer.h index 9c9a641e1e..132198590e 100644 --- a/ijkmedia/ijkplayer/ijkplayer.h +++ b/ijkmedia/ijkplayer/ijkplayer.h @@ -232,8 +232,10 @@ int ijkmp_get_audio_frame_cache_remaining(IjkMediaPlayer *mp); /* register audio samples observer*/ void ijkmp_set_audio_sample_observer(IjkMediaPlayer *mp, ijk_audio_samples_callback cb); /* toggle accurate seek */ -void ijk_set_enable_accurate_seek(IjkMediaPlayer *mp,int open); +void ijkmp_set_enable_accurate_seek(IjkMediaPlayer *mp, int open); /* step to next frame */ -void ijk_step_to_next_frame(IjkMediaPlayer *mp); +void ijkmp_step_to_next_frame(IjkMediaPlayer *mp); +typedef struct IJKSDLSubtitlePreference IJKSDLSubtitlePreference; +void ijkmp_set_subtitle_preference(IjkMediaPlayer *mp, IJKSDLSubtitlePreference* sp); #endif diff --git a/ijkmedia/ijksdl/apple/IJKSDLHudControl.m b/ijkmedia/ijksdl/apple/IJKSDLHudControl.m index d89dbeae92..e26dc4fde6 100644 --- a/ijkmedia/ijksdl/apple/IJKSDLHudControl.m +++ b/ijkmedia/ijksdl/apple/IJKSDLHudControl.m @@ -56,17 +56,6 @@ @interface IJKSDLHudControl () @implementation IJKSDLHudControl -// for debug -//- (instancetype)initWithFrame:(NSRect)frameRect -//{ -// self = [super initWithFrame:frameRect]; -// if (self) { -// [self setWantsLayer:YES]; -// self.layer.backgroundColor = [[NSColor blueColor] CGColor]; -// } -// return self; -//} - - (NSMutableDictionary *)keyIndexes { if (!_keyIndexes) { @@ -133,7 +122,6 @@ - (NSScrollView *)prepareContentView scrollView.hasVerticalScroller = NO; scrollView.hasHorizontalScroller = NO; scrollView.drawsBackground = NO; - NSTableView *tableView = [[NSTableView alloc] initWithFrame:self.view.bounds]; tableView.autoresizingMask = NSViewHeightSizable | NSViewWidthSizable; tableView.intercellSpacing = NSMakeSize(0, 0); diff --git a/ijkmedia/ijksdl/apple/IJKSDLTextureString.h b/ijkmedia/ijksdl/apple/IJKSDLTextureString.h deleted file mode 100755 index 8b52878a6b..0000000000 --- a/ijkmedia/ijksdl/apple/IJKSDLTextureString.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * IJKSDLTextureString.h - * - * Copyright (c) 2013-2014 Bilibili - * Copyright (c) 2013-2014 Zhang Rui - * - * This file is part of ijkPlayer. - * - * ijkPlayer is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * ijkPlayer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with ijkPlayer; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#import - -#if TARGET_OS_OSX -#import -#else -#import -#define NSColor UIColor -#define NSSize CGSize -#define NSFont UIFont -#define NSEdgeInsets UIEdgeInsets -#define NSEdgeInsetsMake UIEdgeInsetsMake -#define NSEdgeInsetsEqual UIEdgeInsetsEqualToEdgeInsets -#endif - - -@interface IJKSDLTextureString : NSObject - -// designated initializer -- (id)initWithAttributedString:(NSAttributedString *)attributedString withBoxColor:(NSColor *)color withBorderColor:(NSColor *)color; - -- (id)initWithString:(NSString *)aString withAttributes:(NSDictionary *)attribs withBoxColor:(NSColor *)color withBorderColor:(NSColor *)color; - -// basic methods that pick up defaults -- (id)initWithString:(NSString *)aString withAttributes:(NSDictionary *)attribs; -- (id)initWithAttributedString:(NSAttributedString *)attributedString; - -// these will force the texture to be regenerated at the next draw - -//the string attributes NSForegroundColorAttribute -@property (nonatomic, strong) NSColor *textColor; -//background box color -@property (nonatomic, strong) NSColor *boxColor; -//border color,default is nil -@property (nonatomic, strong) NSColor *borderColor; -// set top,right,bottom,left margin -@property (nonatomic, assign) NSEdgeInsets edgeInsets; -@property(nonatomic, assign) float cRadius; // Corner radius, if 0 just a rectangle. Defaults to 3.0f - -@property (nonatomic, assign) BOOL antialias; - -- (void)setAttributedString:(NSAttributedString *)attributedString; // set string after initial creation -- (void)setString:(NSString *)aString withAttributes:(NSDictionary *)attribs; // set string after initial creation -- (CVPixelBufferRef)createPixelBuffer; - -@end - diff --git a/ijkmedia/ijksdl/apple/IJKSDLTextureString.m b/ijkmedia/ijksdl/apple/IJKSDLTextureString.m deleted file mode 100755 index 5fd3e3f495..0000000000 --- a/ijkmedia/ijksdl/apple/IJKSDLTextureString.m +++ /dev/null @@ -1,477 +0,0 @@ -/* - * IJKSDLTextureString.m - * - * Copyright (c) 2013-2014 Bilibili - * Copyright (c) 2013-2014 Zhang Rui - * - * This file is part of ijkPlayer. - * - * ijkPlayer is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * ijkPlayer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with ijkPlayer; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -//Download Sample Code https://developer.apple.com/library/archive/samplecode/CocoaGL/Introduction/Intro.html -//https://developer.apple.com/library/archive/qa/qa1829/_index.html -//https://stackoverflow.com/questions/46879895/byte-per-row-is-wrong-when-creating-a-cvpixelbuffer-with-width-multiple-of-90 -//https://github.com/johnboiles/obs-mac-virtualcam/blob/4bd585204ae220068bd55eddf7239b9c8fd8b1dc/src/dal-plugin/Stream.mm - -#import "IJKSDLTextureString.h" - -// The following is a NSBezierPath category to allow -// for rounded corners of the border -#if TARGET_OS_OSX -#pragma mark - -#pragma mark NSBezierPath Category - -@interface NSBezierPath (RoundRect) -+ (NSBezierPath *)bezierPathWithRoundedRect:(NSRect)rect cornerRadius:(float)radius; - -- (void)appendBezierPathWithRoundedRect:(NSRect)rect cornerRadius:(float)radius; -@end - -@implementation NSBezierPath (RoundRect) - -+ (NSBezierPath *)bezierPathWithRoundedRect:(NSRect)rect cornerRadius:(float)radius { - NSBezierPath *result = [NSBezierPath bezierPath]; - [result appendBezierPathWithRoundedRect:rect cornerRadius:radius]; - return result; -} - -- (void)appendBezierPathWithRoundedRect:(NSRect)rect cornerRadius:(float)radius { - if (!NSIsEmptyRect(rect)) { - if (radius > 0.0) { - // Clamp radius to be no larger than half the rect's width or height. - float clampedRadius = MIN(radius, 0.5 * MIN(rect.size.width, rect.size.height)); - - NSPoint topLeft = NSMakePoint(NSMinX(rect), NSMaxY(rect)); - NSPoint topRight = NSMakePoint(NSMaxX(rect), NSMaxY(rect)); - NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect)); - - [self moveToPoint:NSMakePoint(NSMidX(rect), NSMaxY(rect))]; - [self appendBezierPathWithArcFromPoint:topLeft toPoint:rect.origin radius:clampedRadius]; - [self appendBezierPathWithArcFromPoint:rect.origin toPoint:bottomRight radius:clampedRadius]; - [self appendBezierPathWithArcFromPoint:bottomRight toPoint:topRight radius:clampedRadius]; - [self appendBezierPathWithArcFromPoint:topRight toPoint:topLeft radius:clampedRadius]; - [self closePath]; - } else { - // When radius == 0.0, this degenerates to the simple case of a plain rectangle. - [self appendBezierPathWithRect:rect]; - } - } -} - -@end - -#endif - -#pragma mark - -#pragma mark IJKSDLTextureString - -// IJKSDLTextureString follows - -@interface IJKSDLTextureString () - -@property(nonatomic, strong) NSAttributedString * attributedString; -@property(nonatomic, assign) BOOL requiresUpdate; - -@end - -@implementation IJKSDLTextureString - -#pragma mark - -#pragma mark Initializers - -// designated initializer -- (id)initWithAttributedString:(NSAttributedString *)attributedString withBoxColor:(NSColor *)box withBorderColor:(NSColor *)border -{ - self = [super init]; - - self.attributedString = attributedString; - - self.boxColor = box; - self.borderColor = border; - self.antialias = YES; - self.requiresUpdate = YES; - return self; -} - -- (id)initWithString:(NSString *)aString withAttributes:(NSDictionary *)attribs withBoxColor:(NSColor *)box withBorderColor:(NSColor *)border -{ - return [self initWithAttributedString:[[NSAttributedString alloc] initWithString:aString attributes:attribs] withBoxColor:box withBorderColor:border]; -} - -// basic methods that pick up defaults -- (id)initWithAttributedString:(NSAttributedString *)attributedString; -{ - return [self initWithAttributedString:attributedString withBoxColor:[NSColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f] withBorderColor:[NSColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f]]; -} - -- (id)initWithString:(NSString *)aString withAttributes:(NSDictionary *)attribs -{ - return [self initWithAttributedString:[[NSAttributedString alloc] initWithString:aString attributes:attribs] withBoxColor:[NSColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f] withBorderColor:[NSColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f]]; -} - -#pragma mark - -#pragma mark Accessors - - -#pragma mark Text Color - -- (void)setTextColor:(NSColor *)color // set default text color -{ - NSMutableDictionary * stanStringAttrib = [NSMutableDictionary dictionary]; - [stanStringAttrib setObject:color forKey:NSForegroundColorAttributeName]; - - NSMutableAttributedString *aAttributedString = [[NSMutableAttributedString alloc]initWithAttributedString:self.attributedString]; - [aAttributedString addAttributes:stanStringAttrib range:NSMakeRange(0, [aAttributedString length])]; - self.attributedString = [aAttributedString copy]; - self.requiresUpdate = YES; -} - -- (NSColor *)textColor -{ - __block NSColor *aColor = nil; - [self.attributedString enumerateAttribute:NSForegroundColorAttributeName inRange:NSMakeRange(0, [self.attributedString length]) options:0 usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - if (value) { - aColor = value; - *stop = YES; - } - }]; - return aColor; -} - -#pragma mark Box Color - -- (void)setBoxColor:(NSColor *)boxColor -{ - if (_boxColor != boxColor) { - _boxColor = boxColor; - self.requiresUpdate = YES; - } -} - -#pragma mark Border Color - -- (void)setBorderColor:(NSColor *)borderColor -{ - if (_borderColor != borderColor) { - _borderColor = borderColor; - self.requiresUpdate = YES; - } -} - -#pragma mark Margin Size - -// these will force the texture to be regenerated at the next draw -- (void)setEdgeInsets:(NSEdgeInsets)edgeInsets -{ - if (!NSEdgeInsetsEqual(_edgeInsets, edgeInsets)) { - _edgeInsets = edgeInsets; - self.requiresUpdate = YES; - } -} - -#pragma mark Antialiasing - -- (void)setAntialias:(BOOL)antialias -{ - if (_antialias != antialias) { - _antialias = antialias; - self.requiresUpdate = YES; - } -} - -#pragma mark String - -- (CGSize)size -{ - //on retina screen auto return 2x size. - CGSize frameSize = [self.attributedString size]; // current string size - return CGSizeMake(ceilf(frameSize.width), ceilf(frameSize.height)); -} - -- (void)setAttributedString:(NSAttributedString *)attributedString -{ - if (_attributedString != attributedString) { - NSRange fullRange = NSMakeRange(0, [attributedString.string length]); - if (![attributedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:&fullRange]) { - NSMutableParagraphStyle *pghStyle = [[NSMutableParagraphStyle alloc] init]; - pghStyle.alignment = NSTextAlignmentCenter; - pghStyle.lineSpacing = 10; - //pghStyle.lineBreakMode = NSLineBreakByTruncatingTail; - - NSMutableAttributedString * myAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:attributedString]; - [myAttributedString addAttribute:NSParagraphStyleAttributeName value:pghStyle range:fullRange]; - _attributedString = myAttributedString; - } else { - _attributedString = attributedString; - } - - self.requiresUpdate = YES; - } -} - -- (void)setString:(NSString *)aString withAttributes:(NSDictionary *)attribs; // set string after initial creation -{ - [self setAttributedString:[[NSAttributedString alloc] initWithString:aString attributes:attribs]]; -} - -#if TARGET_OS_OSX - -- (void)drawBg:(CGSize)bgSize -{ - CGPoint originPoint = CGPointZero; - NSAffineTransform *transform = nil; - if (!CGPointEqualToPoint(originPoint, CGPointZero)) { - transform = [NSAffineTransform transform] ; - [transform translateXBy:originPoint.x yBy:originPoint.y]; - } - - if ([self.boxColor alphaComponent]) { // this should be == 0.0f but need to make sure - [self.boxColor set]; - NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(NSMakeRect(0.0f, 0.0f, bgSize.width, bgSize.height) , 0.5, 0.5) cornerRadius:self.cRadius]; - if (transform) { - [path transformUsingAffineTransform:transform]; - } - [path fill]; - } - - if ([self.borderColor alphaComponent]) { - [self.borderColor set]; - NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(NSMakeRect (0.0f, 0.0f, bgSize.width, bgSize.height), 0.5, 0.5) - cornerRadius:self.cRadius]; - [path setLineWidth:2.0f]; - if (transform) { - [path transformUsingAffineTransform:transform]; - } - [path stroke]; - } -} - -- (void)drawText:(NSRect)rect -{ - // draw at offset position - [self.attributedString drawInRect:rect]; -// NSStringDrawingContext *ctx = [[NSStringDrawingContext alloc] init]; -// ctx.minimumScaleFactor = [[[NSScreen screens] firstObject] backingScaleFactor]; -// [self.attributedString drawWithRect:rect options:NSStringDrawingUsesLineFragmentOrigin context:ctx]; -// 不能左右居中 -// [self.attributedString drawAtPoint:NSMakePoint(self.edgeInsets.left + originPoint.x, self.edgeInsets.top + originPoint.y)]; -} - -- (NSImage *)image -{ - CGSize picSize = [self size];// CGSizeMake(frameSize.width + 20, frameSize.height + 40); - - NSImage * image = [[NSImage alloc] initWithSize:picSize]; - [image lockFocus]; - - [[NSGraphicsContext currentContext] setShouldAntialias:self.antialias]; - - float width = picSize.width; - float height = picSize.height; - - width += self.edgeInsets.left + self.edgeInsets.right; // add padding - height += self.edgeInsets.top + self.edgeInsets.bottom; // add padding - - [self drawBg:(CGSize){width,height}]; - - NSRect rect = NSMakeRect(self.edgeInsets.left, self.edgeInsets.top, picSize.width, picSize.height); - [self drawText:rect]; - - [image unlockFocus]; - return image; -} - -- (CVPixelBufferRef)createPixelBuffer -{ - CGSize picSize = [self size]; - //(width = 285914, height = 397) - // when width > 16384 or height > 16384 will cause CGLTexImageIOSurface2D return error code kCGLBadValue(10008) invalid numerical value. - if (picSize.width > 1<<14 || picSize.height > 1<<14) { - return NULL; - } - - NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:YES], kCVPixelBufferOpenGLCompatibilityKey, - [NSNumber numberWithBool:YES], kCVPixelBufferMetalCompatibilityKey, - [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, - [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, - [NSDictionary dictionary],kCVPixelBufferIOSurfacePropertiesKey, - nil]; - CVPixelBufferRef pxbuffer = NULL; - - size_t height = (size_t)picSize.height; - size_t width = (size_t)picSize.width; - - width += self.edgeInsets.left + self.edgeInsets.right; // add padding - height += self.edgeInsets.top + self.edgeInsets.bottom; // add padding - - if (height == 0 || height == 0) { - return NULL; - } - - CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, width, - height, kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef)options, - &pxbuffer); - - if (status != kCVReturnSuccess) { - NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); - return NULL; - } - - CVPixelBufferLockBaseAddress(pxbuffer, 0); - void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); - NSParameterAssert(pxdata != NULL); - - CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); - size_t bpr = CVPixelBufferGetBytesPerRow(pxbuffer);//not use 4 * width - CGContextRef context = CGBitmapContextCreate(pxdata, width, height, 8, bpr, rgbColorSpace, kCGImageAlphaPremultipliedLast); - - if (!context) { - CGColorSpaceRelease(rgbColorSpace); - CVPixelBufferUnlockBaseAddress(pxbuffer, 0); - return NULL; - } - - NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]; - [graphicsContext setShouldAntialias:self.antialias]; - [graphicsContext setImageInterpolation:NSImageInterpolationHigh]; - - [NSGraphicsContext saveGraphicsState]; - [NSGraphicsContext setCurrentContext:graphicsContext]; - - [self drawBg:(CGSize){width,height}]; - - NSRect rect = NSMakeRect(self.edgeInsets.left, self.edgeInsets.top, picSize.width, picSize.height); - [self drawText:rect]; - - [NSGraphicsContext restoreGraphicsState]; - - CGColorSpaceRelease(rgbColorSpace); - CGContextRelease(context); - CVPixelBufferUnlockBaseAddress(pxbuffer, 0); - - return pxbuffer; -} - -#else - -- (void)drawBg:(CGSize)picSize -{ - CGPoint originPoint = CGPointZero; - - CGAffineTransform transform = CGAffineTransformIdentity; - if (CGPointEqualToPoint(CGPointZero, originPoint)) { - transform = CGAffineTransformMakeTranslation(originPoint.x, originPoint.y); - } - - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(CGRectMake (0.0f, 0.0f, picSize.width, picSize.height) , 0.5, 0.5) cornerRadius:self.cRadius]; - [path setLineWidth:1.0f]; - if (!CGAffineTransformIsIdentity(transform)) { - [path applyTransform:transform]; - } - [path addClip]; - - if (CGColorGetAlpha(self.boxColor.CGColor)) { // this should be == 0.0f but need to make sure - [self.boxColor set]; - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(CGRectMake (0.0f, 0.0f, picSize.width, picSize.height) , 0.5, 0.5) cornerRadius:0]; - if (!CGAffineTransformIsIdentity(transform)) { - [path applyTransform:transform]; - } - [path fill]; - } - - if (CGColorGetAlpha(self.borderColor.CGColor)) { - [self.borderColor set]; - - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(CGRectMake (0.0f, 0.0f, picSize.width, picSize.height) , 0.5, 0.5) cornerRadius:self.cRadius]; - [path setLineWidth:2.0f]; - if (!CGAffineTransformIsIdentity(transform)) { - [path applyTransform:transform]; - } - [path stroke]; - } -} - -- (void)drawText:(CGRect)rect -{ - NSStringDrawingContext *ctx = [[NSStringDrawingContext alloc] init]; - ctx.minimumScaleFactor = [[UIScreen mainScreen] scale]; - [self.attributedString drawWithRect:rect options:NSStringDrawingUsesLineFragmentOrigin context:ctx]; -} - -- (CVPixelBufferRef)createPixelBuffer -{ - CGSize picSize = [self size]; - //(width = 285914, height = 397) - // when width > 16384 or height > 16384 will cause CGLTexImageIOSurface2D return error code kCGLBadValue(10008) invalid numerical value. - if (picSize.width > 1<<14 || picSize.height > 1<<14) { - return NULL; - } - - NSDictionary* options = @{ - (__bridge NSString*)kCVPixelBufferOpenGLESCompatibilityKey : @YES, - (__bridge NSString*)kCVPixelBufferIOSurfaceOpenGLESTextureCompatibilityKey : [NSDictionary dictionary] -#if !TARGET_OS_SIMULATOR && TARGET_OS_IOS - ,(__bridge NSString*)kCVPixelBufferIOSurfacePropertiesKey: [NSDictionary dictionary] -#endif - }; - - CVPixelBufferRef pxbuffer = NULL; - - size_t height = (size_t)picSize.height; - size_t width = (size_t)picSize.width; - - width += self.edgeInsets.left + self.edgeInsets.right; // add padding - height += self.edgeInsets.top + self.edgeInsets.bottom; // add padding - - CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, width, - height, kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef)options, - &pxbuffer); - if (status == kCVReturnSuccess) { - CVPixelBufferLockBaseAddress(pxbuffer, 0); - void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); - - CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); - size_t bpr = CVPixelBufferGetBytesPerRow(pxbuffer);//not use 4 * width - CGContextRef context = CGBitmapContextCreate(pxdata, width, height, 8, bpr, rgbColorSpace, kCGImageAlphaPremultipliedLast); - - UIGraphicsPushContext(context); - - CGContextTranslateCTM(context, 0, height); - CGContextScaleCTM(context, 1.0, -1.0); - - [self drawBg:(CGSize){width,height}]; - - CGRect rect = CGRectMake(self.edgeInsets.left, self.edgeInsets.top, picSize.width, picSize.height); - [self drawText:rect]; - - UIGraphicsPopContext(); - - CGColorSpaceRelease(rgbColorSpace); - CGContextRelease(context); - - CVPixelBufferUnlockBaseAddress(pxbuffer, 0); - - return pxbuffer; - } else { - NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); - return NULL; - } -} - -#endif - -@end diff --git a/ijkmedia/ijksdl/apple/ijksdl_gpu_metal.h b/ijkmedia/ijksdl/apple/ijksdl_gpu_metal.h new file mode 100644 index 0000000000..bc093b19f3 --- /dev/null +++ b/ijkmedia/ijksdl/apple/ijksdl_gpu_metal.h @@ -0,0 +1,13 @@ +// +// ijksdl_gpu_metal.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/14. +// + +#import + +@protocol MTLDevice; +typedef struct SDL_GPU SDL_GPU; + +SDL_GPU *SDL_CreateGPU_WithMTLDevice(iddevice); diff --git a/ijkmedia/ijksdl/apple/ijksdl_gpu_metal.m b/ijkmedia/ijksdl/apple/ijksdl_gpu_metal.m new file mode 100644 index 0000000000..1cad0e754a --- /dev/null +++ b/ijkmedia/ijksdl/apple/ijksdl_gpu_metal.m @@ -0,0 +1,369 @@ +// +// ijksdl_gpu_metal.m +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/14. +// + +#import "ijksdl_gpu_metal.h" +#import +#import "IJKMetalFBO.h" +#import "IJKMetalSubtitlePipeline.h" +#import "ijksdl_gpu.h" +#include +#import "ijksdl_vout_ios_gles2.h" + +typedef struct SDL_GPU_Opaque_Metal { + iddevice; + idcommandQueue; +} SDL_GPU_Opaque_Metal; + +typedef struct SDL_TextureOverlay_Opaque_Metal { + idtexture; +} SDL_TextureOverlay_Opaque_Metal; + +typedef struct SDL_FBOOverlay_Opaque_Metal { + SDL_TextureOverlay *texture; + IJKMetalFBO* fbo; + idcommandQueue; + id renderEncoder; + id parallelRenderEncoder; + id commandBuffer; + IJKMetalSubtitlePipeline*subPipeline; +} SDL_FBOOverlay_Opaque_Metal; + +static void* getTexture(SDL_TextureOverlay *overlay); + +#pragma mark - Texture Metal + +static void replaceRegion(SDL_TextureOverlay *overlay, SDL_Rectangle rect, void *pixels) +{ + if (overlay && overlay->opaque) { + SDL_TextureOverlay_Opaque_Metal *op = overlay->opaque; + if (op->texture) { + if (rect.x + rect.w > op->texture.width) { + rect.x = 0; + rect.w = (int)op->texture.width; + } + + if (rect.y + rect.h > op->texture.height) { + rect.y = 0; + rect.h = (int)op->texture.height; + } + + overlay->dirtyRect = SDL_union_rectangle(overlay->dirtyRect, rect); + + int bpr = rect.stride; + MTLRegion region = { + {rect.x, rect.y, 0}, // MTLOrigin + {rect.w, rect.h, 1} // MTLSize + }; + + [op->texture replaceRegion:region + mipmapLevel:0 + withBytes:pixels + bytesPerRow:bpr]; + } + } +} + +static void clearMetalRegion(SDL_TextureOverlay *overlay) +{ + if (!overlay) { + return; + } + + if (isZeroRectangle(overlay->dirtyRect)) { + return; + } + + void *pixels = av_mallocz(overlay->dirtyRect.stride * overlay->dirtyRect.h); + replaceRegion(overlay, overlay->dirtyRect, pixels); + av_free(pixels); + overlay->dirtyRect = SDL_Zero_Rectangle; +} + +static void dealloc_texture(SDL_TextureOverlay *overlay) +{ + if (overlay) { + SDL_TextureOverlay_Opaque_Metal *opaque = overlay->opaque; + if (opaque) { + opaque->texture = NULL; + free(opaque); + } + overlay->opaque = NULL; + } +} + +static SDL_TextureOverlay * create_textureOverlay_with_mtlTexture(id subTexture) +{ + if (!subTexture) { + return NULL; + } + + SDL_TextureOverlay *texture = (SDL_TextureOverlay*) calloc(1, sizeof(SDL_TextureOverlay)); + if (!texture) + return NULL; + + SDL_TextureOverlay_Opaque_Metal *opaque = (SDL_TextureOverlay_Opaque_Metal*) calloc(1, sizeof(SDL_TextureOverlay_Opaque_Metal)); + if (!opaque) { + free(texture); + return NULL; + } + + opaque->texture = subTexture; + texture->opaque = opaque; + texture->w = (int)subTexture.width; + texture->h = (int)subTexture.height; + texture->refCount = 1; + + texture->replaceRegion = replaceRegion; + texture->getTexture = getTexture; + texture->clearDirtyRect = clearMetalRegion; + texture->dealloc = dealloc_texture; + + return texture; +} + +static SDL_TextureOverlay *createMetalTexture(iddevice, int w, int h, SDL_TEXTURE_FMT fmt) +{ + MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init]; + + // Indicate that each pixel has a blue, green, red, and alpha channel, where each channel is + // an 8-bit unsigned normalized value (i.e. 0 maps to 0.0 and 255 maps to 1.0) + textureDescriptor.pixelFormat = fmt == SDL_TEXTURE_FMT_A8 ? MTLPixelFormatA8Unorm : MTLPixelFormatBGRA8Unorm; + + // Set the pixel dimensions of the texture + + textureDescriptor.width = w; + textureDescriptor.height = h; + + // Create the texture from the device by using the descriptor + id subTexture = [device newTextureWithDescriptor:textureDescriptor]; + + return create_textureOverlay_with_mtlTexture(subTexture); +} + +#pragma mark - Texture + +static void* getTexture(SDL_TextureOverlay *overlay) +{ + if (overlay && overlay->opaque) { + SDL_TextureOverlay_Opaque_Metal *opaque = overlay->opaque; + return (__bridge void *)opaque->texture; + } + return NULL; +} + +static SDL_TextureOverlay *createTexture(SDL_GPU *gpu, int w, int h, SDL_TEXTURE_FMT fmt) +{ + if (!gpu && ! gpu->opaque) { + return NULL; + } + + SDL_GPU_Opaque_Metal *gop = gpu->opaque; + return createMetalTexture(gop->device, w, h, fmt); +} + +#pragma mark - FBO Metal + +static SDL_FBOOverlay *createMetalFBO(id device, int w, int h) +{ + if (!device) { + return NULL; + } + + SDL_FBOOverlay *overlay = (SDL_FBOOverlay*) calloc(1, sizeof(SDL_FBOOverlay)); + if (!overlay) + return NULL; + + SDL_FBOOverlay_Opaque_Metal *opaque = (SDL_FBOOverlay_Opaque_Metal*) calloc(1, sizeof(SDL_FBOOverlay_Opaque_Metal)); + if (!opaque) { + free(overlay); + return NULL; + } + + CGSize size = CGSizeMake(w, h); + if (opaque->fbo) { + if (![opaque->fbo canReuse:size]) { + opaque->fbo = nil; + } + } + if (!opaque->fbo) { + opaque->fbo = [[IJKMetalFBO alloc] init:device size:size]; + } + opaque->commandQueue = [device newCommandQueue]; + overlay->opaque = (void *)opaque; + return overlay; +} + +#pragma mark - FBO + +static void beginDraw_fbo(SDL_GPU *gpu, SDL_FBOOverlay *overlay, int ass) +{ + if (!gpu || !gpu->opaque || !overlay || !overlay->opaque) { + return; + } + + SDL_FBOOverlay_Opaque_Metal *fop = overlay->opaque; + SDL_GPU_Opaque_Metal *gop = gpu->opaque; + if (ass) { + + } else { + if (!fop->subPipeline) { + IJKMetalSubtitlePipeline *subPipeline = [[IJKMetalSubtitlePipeline alloc] initWithDevice:gop->device outFormat:IJKMetalSubtitleOutFormatDIRECT]; + if ([subPipeline createRenderPipelineIfNeed]) { + fop->subPipeline = subPipeline; + } + } + + if (fop->subPipeline) { + idcommandBuffer = [fop->commandQueue commandBuffer]; + fop->renderEncoder = [fop->fbo createRenderEncoder:commandBuffer]; + fop->commandBuffer = commandBuffer; + + [fop->subPipeline lock]; + // Set the region of the drawable to draw into. + CGSize viewport = [fop->fbo size]; + [fop->renderEncoder setViewport:(MTLViewport){0.0, 0.0, viewport.width, viewport.height, -1.0, 1.0}]; + } else { + return; + } + } +} + +static void metalDraw(SDL_FBOOverlay *foverlay, SDL_TextureOverlay *toverlay, SDL_Rectangle frame) +{ + if (!foverlay || !toverlay || !foverlay->opaque) { + return; + } + SDL_FBOOverlay_Opaque_Metal *fop = foverlay->opaque; + CGSize viewport = [fop->fbo size]; + CGRect rect = IJKSDL_make_metal_NDC(frame, toverlay->scale, viewport); + [fop->subPipeline updateSubtitleVertexIfNeed:rect]; + idtexture = (__bridge id)toverlay->getTexture(toverlay); + [fop->subPipeline drawTexture:texture encoder:fop->renderEncoder]; +} + +static void drawTexture_fbo(SDL_GPU *gpu, SDL_FBOOverlay *foverlay, SDL_TextureOverlay *toverlay, SDL_Rectangle frame) +{ + if (!foverlay || !toverlay) { + return; + } + metalDraw(foverlay, toverlay, frame); +} + +static void endDraw_fbo(SDL_GPU *gpu, SDL_FBOOverlay *overlay) +{ + if (!overlay || !overlay->opaque) { + return; + } + + SDL_FBOOverlay_Opaque_Metal *fop = overlay->opaque; + + [fop->renderEncoder endEncoding]; + [fop->renderEncoder popDebugGroup]; + [fop->parallelRenderEncoder endEncoding]; + [fop->commandBuffer commit]; + [fop->commandBuffer waitUntilCompleted]; + + fop->renderEncoder = nil; + fop->parallelRenderEncoder = nil; + fop->commandBuffer = nil; + [fop->subPipeline unlock]; +} + +static void clear_fbo(SDL_FBOOverlay *overlay) +{ + +} + +static void dealloc_fbo(SDL_FBOOverlay *overlay) +{ + if (!overlay || !overlay->opaque) { + return; + } + + SDL_FBOOverlay_Opaque_Metal *fop = overlay->opaque; + + SDL_TextureOverlay_Release(&fop->texture); + fop->fbo = nil; + fop->commandQueue = nil; + fop->renderEncoder = nil; + fop->commandBuffer = nil; + fop->subPipeline = nil; + free(fop); +} + +static SDL_TextureOverlay * getTexture_fbo(SDL_FBOOverlay *foverlay) +{ + if (!foverlay || !foverlay->opaque) { + return NULL; + } + + SDL_FBOOverlay_Opaque_Metal *fop = foverlay->opaque; + if (!fop->texture) { + id subTexture = [fop->fbo texture]; + fop->texture = create_textureOverlay_with_mtlTexture(subTexture); + } + return SDL_TextureOverlay_Retain(fop->texture); +} + +static SDL_FBOOverlay *createFBO(SDL_GPU *gpu, int w, int h) +{ + if (!gpu || !gpu->opaque) { + return NULL; + } + + SDL_GPU_Opaque_Metal *gop = gpu->opaque; + SDL_FBOOverlay *overlay = createMetalFBO(gop->device, w, h); + + if (overlay) { + overlay->w = w; + overlay->h = h; + overlay->beginDraw = beginDraw_fbo; + overlay->drawTexture = drawTexture_fbo; + overlay->endDraw = endDraw_fbo; + overlay->clear = clear_fbo; + overlay->getTexture = getTexture_fbo; + overlay->dealloc = dealloc_fbo; + } + return overlay; +} + +#pragma mark - GPU + +static void dealloc_gpu(SDL_GPU *gpu) +{ + if (!gpu || !gpu->opaque) { + return; + } + + SDL_GPU_Opaque_Metal *gop = gpu->opaque; + gop->device = NULL; + gop->commandQueue = NULL; + free(gop); +} + +SDL_GPU *SDL_CreateGPU_WithMTLDevice(iddevice) +{ + if (!device) { + return NULL; + } + SDL_GPU *gl = (SDL_GPU*) calloc(1, sizeof(SDL_GPU)); + if (!gl) + return NULL; + + SDL_GPU_Opaque_Metal *opaque = av_mallocz(sizeof(SDL_GPU_Opaque_Metal)); + if (!opaque) { + free(gl); + return NULL; + } + opaque->device = device; + gl->opaque = opaque; + gl->createTexture = createTexture; + gl->createFBO = createFBO; + gl->dealloc = dealloc_gpu; + + return gl; +} diff --git a/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_fbo_macos.h b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_fbo_macos.h new file mode 100644 index 0000000000..649d6a7a9a --- /dev/null +++ b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_fbo_macos.h @@ -0,0 +1,24 @@ +// +// ijksdl_gpu_opengl_fbo_macos.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/15. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol IJKSDLSubtitleTextureWrapper; +@interface IJKSDLOpenGLFBO : NSObject + +@property(nonatomic, readonly) id texture; + +- (instancetype)initWithSize:(CGSize)size; +- (BOOL)canReuse:(CGSize)size; +- (CGSize)size; +- (void)bind; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_fbo_macos.m b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_fbo_macos.m new file mode 100644 index 0000000000..d9c07451a1 --- /dev/null +++ b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_fbo_macos.m @@ -0,0 +1,99 @@ +// +// ijksdl_gpu_opengl_fbo_macos.m +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/15. +// + +#import "ijksdl_gpu_opengl_fbo_macos.h" +#import "ijksdl_gles2.h" +#import "ijksdl_vout_ios_gles2.h" +#include + +@interface IJKSDLOpenGLFBO() + +@property(nonatomic, assign) GLuint fbo; +@property(nonatomic, readwrite) id texture; + +@end + +@implementation IJKSDLOpenGLFBO + +- (void)dealloc +{ + //the fbo was created in vout thread, so must keep delete fbo in same thread. + //and now fbo is ffsub property,we destroy ffsub in vout thread. + if (_fbo) { + glDeleteFramebuffers(1, &_fbo); + } + _texture = nil; +} + +- (instancetype)initWithSize:(CGSize)size +{ + self = [super init]; + if (self) { + uint32_t t; + // Create a texture object that you apply to the model. + glGenTextures(1, &t); + GLenum target = GL_TEXTURE_RECTANGLE;//GL_TEXTURE_2D + glBindTexture(target, t); + + // Set up filter and wrap modes for the texture object. + glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + // Allocate a texture image to which you can render to. Pass `NULL` for the data parameter + // becuase you don't need to load image data. You generate the image by rendering to the texture. + glTexImage2D(target, 0, GL_RGBA, size.width, size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + glGenFramebuffers(1, &_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, _fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, t, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (GL_FRAMEBUFFER_COMPLETE == status) { + _texture = IJKSDL_crate_openglTextureWrapper(t, size.width, size.height); + glBindTexture(target, 0); + return self; + } else { + glBindTexture(target, 0); + av_log(NULL, AV_LOG_ERROR, "CheckFramebufferStatus:%x\n",status); + #if DEBUG + NSAssert(NO, @"Failed to make complete framebuffer object %x.", status); + #endif + return nil; + } + } + return nil; +} + +// Create texture and framebuffer objects to render and snapshot. +- (BOOL)canReuse:(CGSize)size +{ + if (CGSizeEqualToSize(CGSizeZero, size)) { + return NO; + } + + if ([self.texture w] == (int)size.width && [self.texture h] == (int)size.height && _fbo && _texture) { + return YES; + } else { + return NO; + } +} + +- (CGSize)size +{ + return CGSizeMake([self.texture w], [self.texture h]); +} + +- (void)bind +{ + // Bind the snapshot FBO and render the scene. + glBindFramebuffer(GL_FRAMEBUFFER, _fbo); +} + +@end + diff --git a/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_macos.h b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_macos.h new file mode 100644 index 0000000000..0adac52e82 --- /dev/null +++ b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_macos.h @@ -0,0 +1,13 @@ +// +// ijksdl_gpu_opengl_macos.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/14. +// + +#import + +@class NSOpenGLContext; +typedef struct SDL_GPU SDL_GPU; + +SDL_GPU *SDL_CreateGPU_WithGLContext(NSOpenGLContext * context); diff --git a/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_macos.m b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_macos.m new file mode 100644 index 0000000000..5f237c8a33 --- /dev/null +++ b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_macos.m @@ -0,0 +1,356 @@ +// +// ijksdl_gpu_opengl_macos.m +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/14. +// + +#import "ijksdl_gpu_opengl_macos.h" +#import "ijksdl_gpu_opengl_fbo_macos.h" +#import "ijksdl_gpu_opengl_renderer_macos.h" +#import "ijksdl_gles2.h" +#import "ijksdl_vout_ios_gles2.h" +#import "ijksdl_gpu.h" +#include + + +typedef struct SDL_GPU_Opaque_GL { + NSOpenGLContext *glContext; +} SDL_GPU_Opaque_GL; + +typedef struct SDL_TextureOverlay_Opaque_GL { + id texture; + NSOpenGLContext *glContext; +} SDL_TextureOverlay_Opaque_GL; + +typedef struct SDL_FBOOverlay_Opaque_GL { + NSOpenGLContext *glContext; + SDL_TextureOverlay *toverlay; + IJKSDLOpenGLFBO *fbo; + IJKSDLOpenGLSubRenderer *renderer; +} SDL_FBOOverlay_Opaque_GL; + +static void* getTexture(SDL_TextureOverlay *overlay); + +#pragma mark - Texture OpenGL + +static void replaceRegion(SDL_TextureOverlay *overlay, SDL_Rectangle rect, void *pixels) +{ + if (overlay && overlay->opaque) { + + SDL_TextureOverlay_Opaque_GL *op = overlay->opaque; + idt = op->texture; + if (rect.x + rect.w > t.w) { + rect.x = 0; + rect.w = t.w; + } + + if (rect.y + rect.h > t.h) { + rect.y = 0; + rect.h = t.h; + } + overlay->dirtyRect = SDL_union_rectangle(overlay->dirtyRect, rect); + + CGLLockContext([op->glContext CGLContextObj]); + [op->glContext makeCurrentContext]; + glBindTexture(GL_TEXTURE_RECTANGLE, t.texture); + glTexSubImage2D(GL_TEXTURE_RECTANGLE, 0, rect.x, rect.y, (GLsizei)rect.w, (GLsizei)rect.h, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)pixels); + //macOS 10.14及以下系统,需要glFlush操作,多线程共享该纹理,否者导致出现多个拖影,或者只有一部分被替换的奇怪bug。 + glFlush(); + IJK_GLES2_checkError("replaceOpenGlRegion"); + glBindTexture(GL_TEXTURE_RECTANGLE, 0); + CGLUnlockContext([op->glContext CGLContextObj]); + } +} + +static void clearOpenGLRegion(SDL_TextureOverlay *overlay) +{ + if (!overlay) { + return; + } + + if (isZeroRectangle(overlay->dirtyRect)) { + return; + } + + void *pixels = av_mallocz(overlay->dirtyRect.stride * overlay->dirtyRect.h); + replaceRegion(overlay, overlay->dirtyRect, pixels); + av_freep(&pixels); + overlay->dirtyRect = SDL_Zero_Rectangle; +} + +static void dealloc_texture(SDL_TextureOverlay *overlay) +{ + if (overlay) { + SDL_TextureOverlay_Opaque_GL *opaque = overlay->opaque; + if (opaque) { + opaque->glContext = nil; + opaque->texture = nil; + free(opaque); + } + overlay->opaque = NULL; + } +} + +static SDL_TextureOverlay * create_textureOverlay_with_glTexture(NSOpenGLContext *context, id subTexture) +{ + SDL_TextureOverlay *overlay = (SDL_TextureOverlay*) calloc(1, sizeof(SDL_TextureOverlay)); + if (!overlay) + return NULL; + + SDL_TextureOverlay_Opaque_GL *opaque = (SDL_TextureOverlay_Opaque_GL*)calloc(1, sizeof(SDL_TextureOverlay_Opaque_GL)); + if (!opaque) { + free(overlay); + return NULL; + } + + opaque->glContext = context; + opaque->texture = subTexture; + overlay->opaque = opaque; + overlay->w = subTexture.w; + overlay->h = subTexture.h; + overlay->refCount = 1; + + overlay->replaceRegion = replaceRegion; + overlay->getTexture = getTexture; + overlay->clearDirtyRect = clearOpenGLRegion; + overlay->dealloc = dealloc_texture; + + return overlay; +} + +static SDL_TextureOverlay *createOpenGLTexture(NSOpenGLContext *context, int w, int h, SDL_TEXTURE_FMT fmt) +{ + CGLLockContext([context CGLContextObj]); + [context makeCurrentContext]; + uint32_t texture; + // Create a texture object that you apply to the model. + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_RECTANGLE, texture); + glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glBindTexture(GL_TEXTURE_RECTANGLE, 0); + CGLUnlockContext([context CGLContextObj]); + IJK_GLES2_checkError("create OpenGL Texture"); + id t = IJKSDL_crate_openglTextureWrapper(texture, w, h); + return create_textureOverlay_with_glTexture(context, t); +} + +static void* getTexture(SDL_TextureOverlay *overlay) +{ + if (overlay && overlay->opaque) { + SDL_TextureOverlay_Opaque_GL *opaque = overlay->opaque; + return (__bridge void *)opaque->texture; + } + return NULL; +} + +static SDL_TextureOverlay *createTexture(SDL_GPU *gpu, int w, int h, SDL_TEXTURE_FMT fmt) +{ + if (!gpu && ! gpu->opaque) { + return NULL; + } + + SDL_GPU_Opaque_GL *gop = gpu->opaque; + + return createOpenGLTexture(gop->glContext, w, h, fmt); +} + +#pragma mark - FBO OpenGl + +static SDL_FBOOverlay *createOpenGLFBO(NSOpenGLContext *glContext, int w, int h) +{ + SDL_FBOOverlay *overlay = (SDL_FBOOverlay*) calloc(1, sizeof(SDL_FBOOverlay)); + if (!overlay) + return NULL; + + SDL_FBOOverlay_Opaque_GL *opaque = (SDL_FBOOverlay_Opaque_GL*) calloc(1, sizeof(SDL_FBOOverlay_Opaque_GL)); + if (!opaque) { + free(overlay); + return NULL; + } + + CGSize size = CGSizeMake(w, h); + if (opaque->fbo) { + if (![opaque->fbo canReuse:size]) { + opaque->fbo = nil; + } + } + + CGLLockContext([glContext CGLContextObj]); + [glContext makeCurrentContext]; + opaque->fbo = [[IJKSDLOpenGLFBO alloc] initWithSize:size]; + CGLUnlockContext([glContext CGLContextObj]); + + opaque->glContext = glContext; + overlay->opaque = opaque; + return overlay; +} + +static void beginDraw_fbo(SDL_GPU *gpu, SDL_FBOOverlay *overlay, int ass) +{ + if (!gpu || !gpu->opaque || !overlay || !overlay->opaque) { + return; + } + + SDL_FBOOverlay_Opaque_GL *fop = overlay->opaque; + SDL_GPU_Opaque_GL *gop = gpu->opaque; + + if (ass) { + + } else { + if (!fop->renderer) { + fop->renderer = [[IJKSDLOpenGLSubRenderer alloc] init]; + } + + if (fop->renderer) { + CGLLockContext([gop->glContext CGLContextObj]); + [gop->glContext makeCurrentContext]; + [fop->renderer setupOpenGLProgramIfNeed]; + [fop->renderer bindFBO:fop->fbo]; + } + } +} + +static void drawTexture_fbo(SDL_GPU *gpu, SDL_FBOOverlay *foverlay, SDL_TextureOverlay *toverlay, SDL_Rectangle frame) +{ + if (!gpu || !gpu->opaque || !foverlay || !foverlay->opaque || !toverlay) { + return; + } + SDL_FBOOverlay_Opaque_GL *fop = foverlay->opaque; + CGSize viewport = [fop->fbo size]; + CGRect rect = IJKSDL_make_openGL_NDC(frame, toverlay->scale, viewport); + [fop->renderer updateSubtitleVertexIfNeed:rect]; + id texture = (__bridge id)toverlay->getTexture(toverlay); + [fop->renderer drawTexture:texture]; +} + +static void endDraw_fbo(SDL_GPU *gpu, SDL_FBOOverlay *overlay) +{ + if (!gpu || !gpu->opaque || !overlay || !overlay->opaque) { + return; + } + + SDL_GPU_Opaque_GL *gop = gpu->opaque; + glFlush(); + CGLUnlockContext([gop->glContext CGLContextObj]); +} + +static void clear_fbo(SDL_FBOOverlay *overlay) +{ + if (!overlay || !overlay->opaque) { + return; + } + SDL_FBOOverlay_Opaque_GL *fop = overlay->opaque; + [fop->renderer clean]; +} + +static void dealloc_fbo(SDL_FBOOverlay *overlay) +{ + if (!overlay || !overlay->opaque) { + return; + } + + SDL_FBOOverlay_Opaque_GL *fop = overlay->opaque; + fop->fbo = nil; + SDL_TextureOverlay_Release(&fop->toverlay); + fop->glContext = nil; + free(fop); +} + +static SDL_TextureOverlay * getTexture_fbo(SDL_FBOOverlay *foverlay) +{ + if (!foverlay || !foverlay->opaque) { + return NULL; + } + + SDL_FBOOverlay_Opaque_GL *fop = foverlay->opaque; + if (!fop->toverlay) { + id subTexture = [fop->fbo texture]; + fop->toverlay = create_textureOverlay_with_glTexture(fop->glContext, subTexture); + } + return SDL_TextureOverlay_Retain(fop->toverlay); +} + +static SDL_FBOOverlay *createFBO(SDL_GPU *gpu, int w, int h) +{ + if (!gpu || !gpu->opaque) { + return NULL; + } + + SDL_GPU_Opaque_GL *gop = gpu->opaque; + SDL_FBOOverlay *overlay = createOpenGLFBO(gop->glContext, w, h); + + if (overlay) { + overlay->w = w; + overlay->h = h; + overlay->beginDraw = beginDraw_fbo; + overlay->drawTexture = drawTexture_fbo; + overlay->endDraw = endDraw_fbo; + overlay->clear = clear_fbo; + overlay->getTexture = getTexture_fbo; + overlay->dealloc = dealloc_fbo; + } + return overlay; +} + +#pragma mark - GPU + +static void dealloc_gpu(SDL_GPU *gpu) +{ + if (!gpu || !gpu->opaque) { + return; + } + + SDL_GPU_Opaque_GL *gop = gpu->opaque; + gop->glContext = NULL; + free(gop); +} + +static NSOpenGLContext *createGLContext(NSOpenGLContext *sharedContext) +{ + NSOpenGLPixelFormatAttribute attrs[] = + { + NSOpenGLPFAAccelerated, + NSOpenGLPFANoRecovery, + NSOpenGLPFADoubleBuffer, + NSOpenGLPFADepthSize, 24, +#if ! USE_LEGACY_OPENGL + NSOpenGLPFAOpenGLProfile,NSOpenGLProfileVersion3_2Core, +#endif + NSOpenGLPFAAllowOfflineRenderers, 1, + 0 + }; + + NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + + if (pf) + { + return [[NSOpenGLContext alloc] initWithFormat:pf shareContext:sharedContext]; + } + return nil; +} + +SDL_GPU *SDL_CreateGPU_WithGLContext(NSOpenGLContext * context) +{ + SDL_GPU *gl = (SDL_GPU*) calloc(1, sizeof(SDL_GPU)); + if (!gl) + return NULL; + + SDL_GPU_Opaque_GL *opaque = av_mallocz(sizeof(SDL_GPU_Opaque_GL)); + if (!opaque) { + free(gl); + return NULL; + } + opaque->glContext = createGLContext(context); + gl->opaque = opaque; + gl->createTexture = createTexture; + gl->createFBO = createFBO; + gl->dealloc = dealloc_gpu; + return gl; +} diff --git a/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_renderer_macos.h b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_renderer_macos.h new file mode 100644 index 0000000000..a0faf6739f --- /dev/null +++ b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_renderer_macos.h @@ -0,0 +1,21 @@ +// +// ijksdl_gpu_opengl_renderer_macos.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/15. +// + +#import + +@class IJKSDLOpenGLFBO; +@protocol IJKSDLSubtitleTextureWrapper; + +@interface IJKSDLOpenGLSubRenderer : NSObject + +- (void)setupOpenGLProgramIfNeed; +- (void)clean; +- (void)bindFBO:(IJKSDLOpenGLFBO *)fbo; +- (void)updateSubtitleVertexIfNeed:(CGRect)rect; +- (void)drawTexture:(id)subTexture; + +@end diff --git a/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_renderer_macos.m b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_renderer_macos.m new file mode 100644 index 0000000000..d3f563e234 --- /dev/null +++ b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_renderer_macos.m @@ -0,0 +1,215 @@ +// +// ijksdl_gpu_opengl_renderer_macos.m +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/15. +// + +#import "ijksdl_gpu_opengl_renderer_macos.h" +#import "ijksdl_gpu_opengl_shader_compiler.h" +#import "ijksdl_gpu_opengl_fbo_macos.h" +#import "ijksdl_gles2.h" +#import "ijksdl_vout_ios_gles2.h" +// Uniform index. +enum +{ + UNIFORM_S_AI, + DIMENSION_S_AI, + NUM_UNIFORMS_AI +}; + +// Attribute index. +enum +{ + ATTRIB_VERTEX_AI, + ATTRIB_TEXCOORD_AI, + NUM_ATTRIBUTES_AI +}; + +@interface IJKSDLOpenGLSubRenderer() +{ + GLint _uniforms[NUM_UNIFORMS_AI]; + GLint _attributers[NUM_ATTRIBUTES_AI]; + + /// 顶点对象 + GLuint _vbo; + GLuint _vao; +} + +@property (nonatomic) IJKSDLOpenGLCompiler * openglCompiler; +@property (nonatomic) CGRect lastRect; + +@end + +@implementation IJKSDLOpenGLSubRenderer + +- (void)dealloc +{ + glDeleteBuffers(1, &_vbo); + glDeleteVertexArrays(1, &_vao); +} + +#define SDL_STRINGIZE(x) #x +#define SDL_STRINGIZE2(x) SDL_STRINGIZE(x) +#define SDL_STRING(x) @SDL_STRINGIZE2(x) + +- (NSString *)commonVSH +{ + return @"#"SDL_STRING( +version 330\n + +in vec2 texCoord; +in vec2 position; +out vec2 texCoordVarying; + +void main() +{ + gl_Position = vec4(position, 0.0, 1.0); + texCoordVarying = texCoord; +} + ); +} + +- (NSString *)fsh +{ + return @"#"SDL_STRING( +version 330\n + +uniform sampler2DRect SamplerS; +uniform vec2 textureDimensionS; +in vec2 texCoordVarying; +out vec4 fragColor; + +void main() +{ + vec2 texCoord = texCoordVarying * textureDimensionS; + fragColor = texture(SamplerS, texCoord); +} + + ); +} + +- (void)setupOpenGLProgramIfNeed +{ + if (!self.openglCompiler) { + self.openglCompiler = [[IJKSDLOpenGLCompiler alloc] initWithvsh:[self commonVSH] fsh:[self fsh]]; + + if ([self.openglCompiler compileIfNeed]) { + // Get uniform locations. + _uniforms[UNIFORM_S_AI] = [self.openglCompiler getUniformLocation:"SamplerS"]; + _uniforms[DIMENSION_S_AI] = [self.openglCompiler getUniformLocation:"textureDimensionS"]; + + _attributers[ATTRIB_VERTEX_AI] = [self.openglCompiler getAttribLocation:"position"]; + _attributers[ATTRIB_TEXCOORD_AI] = [self.openglCompiler getAttribLocation:"texCoord"]; + + glGenVertexArrays(1, &_vao); + // 创建顶点缓存对象 + glGenBuffers(1, &_vbo); + } + } + [self.openglCompiler active]; +} + +- (void)updateSubtitleVertexIfNeed:(CGRect)rect +{ + if (CGRectEqualToRect(self.lastRect, rect)) { + return; + } + + self.lastRect = rect; + //OpenGL坐标范围只为-1到1 + float x = rect.origin.x; + float y = rect.origin.y; + float w = rect.size.width; + float h = rect.size.height; + /* + triangle strip + ^+ + V3|V4 + --|--->+ + V1|V2 + -->V1V2V3 + -->V2V3V4 + + texture + ^y + |V3 V4 + |V1 V2 + |---->x + */ + + GLfloat quadData [] = { + x, y, + x + w, y, + x, y + h, + x + w, y + h, + //Texture Postition;这里纹理坐标和显示的时候不一样,保证画面方向是正常的,而不是倒立的 + 0, 0, + 1, 0, + 0, 1, + 1, 1, + }; + + // 绑定顶点缓存对象到当前的顶点位置,之后对GL_ARRAY_BUFFER的操作即是对_VBO的操作 + glBindBuffer(GL_ARRAY_BUFFER, _vbo); + // 将CPU数据发送到GPU,数据类型GL_ARRAY_BUFFER + // GL_STATIC_DRAW 表示数据不会被修改,将其放置在GPU显存的更合适的位置,增加其读取速度 + glBufferData(GL_ARRAY_BUFFER, sizeof(quadData), quadData, GL_DYNAMIC_DRAW); + + // 更新顶点数据 + glBindVertexArray(_vao); + glEnableVertexAttribArray(_attributers[ATTRIB_VERTEX_AI]); + glEnableVertexAttribArray(_attributers[ATTRIB_TEXCOORD_AI]); + + // 指定顶点着色器位置为0的参数的数据读取方式与数据类型 + // 第一个参数: 参数位置 + // 第二个参数: 一次读取数据 + // 第三个参数: 数据类型 + // 第四个参数: 是否归一化数据 + // 第五个参数: 间隔多少个数据读取下一次数据 + // 第六个参数: 指定读取第一个数据在顶点数据中的偏移量 + glVertexAttribPointer(_attributers[ATTRIB_VERTEX_AI], 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + IJK_GLES2_checkError_TRACE("glVertexAttribPointer(av4_position)"); + // texture coord attribute + glVertexAttribPointer(_attributers[ATTRIB_TEXCOORD_AI], 2, GL_FLOAT, GL_FALSE, 0, (void*)(8 * sizeof(float))); + IJK_GLES2_checkError_TRACE("glVertexAttribPointer(av2_texcoord)"); +} + +- (void)clean +{ + glClearColor(0.0,0.0,0.0,0.0); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); +} + +- (void)bindFBO:(IJKSDLOpenGLFBO *)fbo +{ + // Bind the FBO + [fbo bind]; + CGSize viewport = [fbo size]; + glViewport(0, 0, viewport.width, viewport.height); +} + +- (void)drawTexture:(id)subTexture +{ + //设置采样器位置,保证了每个uniform采样器对应着正确的纹理单元 + glUniform1i(_uniforms[UNIFORM_S_AI], 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_RECTANGLE, [subTexture texture]); + glUniform2f(_uniforms[DIMENSION_S_AI], subTexture.w, subTexture.h); + + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindVertexArray(_vao); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindTexture(GL_TEXTURE_RECTANGLE, 0); + glBindVertexArray(0); + + IJK_GLES2_checkError("subtitle renderer draw"); +} + +@end diff --git a/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_shader_compiler.h b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_shader_compiler.h new file mode 100644 index 0000000000..8f74411505 --- /dev/null +++ b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_shader_compiler.h @@ -0,0 +1,26 @@ +// +// ijksdl_gpu_opengl_shader_compiler.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/15. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface IJKSDLOpenGLCompiler : NSObject + +@property (copy) NSString *vsh; +@property (copy) NSString *fsh; + +- (instancetype)initWithvsh:(NSString *)vshName + fsh:(NSString *)fshName; +- (BOOL)compileIfNeed; +- (void)active; +- (int)getUniformLocation:(const char *)name; +- (int)getAttribLocation:(const char *)name; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_shader_compiler.m b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_shader_compiler.m new file mode 100644 index 0000000000..62743d0708 --- /dev/null +++ b/ijkmedia/ijksdl/apple/ijksdl_gpu_opengl_shader_compiler.m @@ -0,0 +1,208 @@ +// +// ijksdl_gpu_opengl_shader_compiler.m +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/15. +// + +#import "ijksdl_gpu_opengl_shader_compiler.h" +#import "ijksdl_gles2.h" + +@interface IJKSDLOpenGLCompiler () + +@property uint32_t program; + +@end + +@implementation IJKSDLOpenGLCompiler + +- (void)dealloc +{ + glDeleteProgram(_program); +} + +- (instancetype)initWithvsh:(NSString *)vsh + fsh:(NSString *)fsh +{ + self = [super init]; + if (self) { + self.vsh = vsh; + self.fsh = fsh; + } + return self; +} + +- (BOOL)compileIfNeed +{ + if (self.program) { + return YES; + } else if (self.vsh.length > 0 && self.fsh.length > 0) { + GLuint program = [self compileProgram]; + if (program > 0) { + self.program = program; + return YES; + } else { + return NO; + } + } else { + return NO; + } +} + +- (void)active +{ + if (self.program > 0) { + glUseProgram(self.program); + } +} + +- (int)getUniformLocation:(const char *)name +{ + NSAssert(self.program > 0, @"you must compile opengl program firstly!"); + NSAssert(strlen(name) > 0, @"what's your uniform name?"); + int r = glGetUniformLocation(self.program, name); + IJK_GLES2_checkError("GetUniform"); + return r; +} + +- (int)getAttribLocation:(const char *)name +{ + NSAssert(self.program > 0, @"you must compile opengl program firstly!"); + NSAssert(strlen(name) > 0, @"what's your uniform name?"); + + int r = glGetAttribLocation(self.program, name); + NSAssert(r >= 0, @"get attrib location failed:%s", name); + return r; +} + +- (GLuint)compileProgram +{ + // Create and compile the vertex shader. + GLuint vertShader = [self compileShader:self.vsh type:GL_VERTEX_SHADER]; + IJK_GLES2_checkError("compile vertex shader"); + NSAssert(vertShader, @"Failed to compile vertex shader"); + // Create and compile fragment shader. + GLuint fragShader = [self compileShader:self.fsh type:GL_FRAGMENT_SHADER]; + IJK_GLES2_checkError("compile fragment shader"); + NSAssert(fragShader, @"Failed to compile fragment shader"); + GLuint program = glCreateProgram(); + + // Attach vertex shader to program. + glAttachShader(program, vertShader); + + // Attach fragment shader to program. + glAttachShader(program, fragShader); + + // Bind attribute locations. This needs to be done prior to linking. + //glBindAttribLocation(self.program, ATTRIB_VERTEX, "position"); + //glBindAttribLocation(self.program, ATTRIB_TEXCOORD, "texCoord"); + + // Link the program. + if (![self linkProgram:program]) { + if (vertShader) { + glDeleteShader(vertShader); + vertShader = 0; + } + if (fragShader) { + glDeleteShader(fragShader); + fragShader = 0; + } + if (self.program) { + glDeleteProgram(self.program); + self.program = 0; + } + NSAssert(NO, @"Failed link program:%d",program); + return 0; + } + + // Release vertex and fragment shaders. + if (vertShader) { + glDetachShader(program, vertShader); + glDeleteShader(vertShader); + } + + if (fragShader) { + glDetachShader(program, fragShader); + glDeleteShader(fragShader); + } + + IJK_GLES2_checkError("link program"); + return program; +} + +- (GLuint)compileShader:(NSString *)sourceStr type:(GLenum)type +{ + GLint status; + const GLchar *source = (GLchar *)[sourceStr UTF8String]; + + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + +#if defined(DEBUG) + GLint logLength; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc(logLength); + glGetShaderInfoLog(shader, logLength, &logLength, log); + NSLog(@"Shader compile log:\n%s", log); + free(log); + } +#endif + + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (status == 0) { + glDeleteShader(shader); + return 0; + } + + return shader; +} + +- (BOOL)linkProgram:(GLuint)prog +{ + GLint status; + glLinkProgram(prog); + +#if defined(DEBUG) + GLint logLength; + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc(logLength); + glGetProgramInfoLog(prog, logLength, &logLength, log); + NSLog(@"Program link log:\n%s", log); + free(log); + } +#endif + + glGetProgramiv(prog, GL_LINK_STATUS, &status); + if (status == 0) { + return NO; + } + + return YES; +} + +- (BOOL)validateProgram:(GLuint)prog +{ + GLint logLength, status; + + glValidateProgram(prog); + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc(logLength); + glGetProgramInfoLog(prog, logLength, &logLength, log); + NSLog(@"Program validate log:\n%s", log); + free(log); + } + + glGetProgramiv(prog, GL_VALIDATE_STATUS, &status); + if (status == 0) { + return NO; + } + + return YES; +} + +@end + diff --git a/ijkmedia/ijksdl/apple/ijksdl_ios.h b/ijkmedia/ijksdl/apple/ijksdl_ios.h index c68574e02e..039fe45d69 100644 --- a/ijkmedia/ijksdl/apple/ijksdl_ios.h +++ b/ijkmedia/ijksdl/apple/ijksdl_ios.h @@ -24,31 +24,3 @@ #include "../ijksdl.h" #include "ijksdl_aout_ios_audiounit.h" #import "ijksdl_vout_ios_gles2.h" -#if TARGET_OS_IOS -#import -#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) -#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) -#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) -#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) -#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) - -inline static BOOL isIOS9OrLater() -{ - return SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"9.0"); -} - -inline static BOOL isIOS8OrLater() -{ - return SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0"); -} - -inline static BOOL isIOS7OrLater() -{ - return SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0"); -} - -inline static BOOL isIOS6OrLater() -{ - return SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0"); -} -#endif diff --git a/ijkmedia/ijksdl/apple/ijksdl_vout_ios_gles2.h b/ijkmedia/ijksdl/apple/ijksdl_vout_ios_gles2.h index b7e2cdca01..52d3d6cefe 100644 --- a/ijkmedia/ijksdl/apple/ijksdl_vout_ios_gles2.h +++ b/ijkmedia/ijksdl/apple/ijksdl_vout_ios_gles2.h @@ -23,8 +23,118 @@ #include "ijksdl/ijksdl_stdinc.h" #include "ijksdl/ijksdl_vout.h" +#include "../ijksdl_gpu.h" #import "IJKVideoRenderingProtocol.h" +#if TARGET_OS_OSX +@protocol IJKSDLSubtitleTextureWrapper + +@property(nonatomic) uint32_t texture; +@property(nonatomic) int w; +@property(nonatomic) int h; + +@end + +id IJKSDL_crate_openglTextureWrapper(uint32_t texture, int w, int h); + +// Normalized Device Coordinates +static inline CGRect IJKSDL_make_openGL_NDC(SDL_Rectangle frame, float scale, CGSize viewport) +{ + float swidth = frame.w * scale; + float sheight = frame.h * scale; + + float width = viewport.width; + float height = viewport.height; + + //处理缩放导致的坐标变化 + float sx = frame.x - (scale - 1.0) * frame.w * 0.5; + float sy = frame.y; + + if (sy > height - sheight) { + sy = height - sheight; + } + + if (sy < 0) { + sy = 0; + } + +#define NDC(x) (x * 2.0 - 1.0) + float x = NDC(sx / width); + //将 y 的原始值范围 [0,(height - sheight) / height] 转化到 [-1,1] 标准化范围 + //计算的是从上往下的距离 + float y = NDC(sy / height); +#undef NDC + + /* + (x,y)在左上角,y轴,-1在最上面,1在最下面 + x,y-------- + | | + |---------- + */ + + if (width != 0 && height != 0) { + return (CGRect){ + x, + y, + 2.0 * (swidth / width), + 2.0 * (sheight / height) + }; + } + return CGRectZero; +} + +#endif + +// Normalized Device Coordinates +static inline CGRect IJKSDL_make_metal_NDC(SDL_Rectangle frame, float scale, CGSize viewport) +{ + float swidth = frame.w * scale; + float sheight = frame.h * scale; + + float width = viewport.width; + float height = viewport.height; + + //处理缩放导致的坐标变化 + float sx = frame.x - (scale - 1.0) * frame.w * 0.5; + //图像y轴正方向朝下,加上高度表示的是左下角的坐标; + float sy = frame.y + sheight; + + //左下角往下最多贴着最下面 + if (sy > height) { + sy = height; + } + //左下角往上最少要保持一个内容的高度 + if (sy < sheight) { + sy = sheight; + } + +#define NDC(x) (x * 2.0 - 1.0) + float x = NDC(sx / width); + //计算的是从下往上的距离 + float y = NDC((height - sy) / height); +#undef NDC + + //实际输出 [maxY,-1] + /* + (x,y)在左下角,y轴,1在最上面,-1在最下面 + ---------- + | | + x,y-------- + */ + + if (width != 0 && height != 0) { + return (CGRect){ + x, + y, + 2.0 * (swidth / width), + 2.0 * (sheight / height) + }; + } + return CGRectZero; +} + SDL_Vout *SDL_VoutIos_CreateForGLES2(void); void SDL_VoutIos_SetGLView(SDL_Vout *vout, UIView* view); +SDL_GPU *SDL_CreateGPU_WithContext(id context); + diff --git a/ijkmedia/ijksdl/apple/ijksdl_vout_ios_gles2.m b/ijkmedia/ijksdl/apple/ijksdl_vout_ios_gles2.m index 0b5271edcc..11daf91a4b 100644 --- a/ijkmedia/ijksdl/apple/ijksdl_vout_ios_gles2.m +++ b/ijkmedia/ijksdl/apple/ijksdl_vout_ios_gles2.m @@ -28,22 +28,15 @@ #include "ijksdl/ijksdl_vout_internal.h" #include "ijksdl_vout_overlay_ffmpeg.h" #include "ijksdl_vout_overlay_ffmpeg_hw.h" -#if TARGET_OS_IOS -#include "../ios/IJKSDLGLView.h" -#else +#include "ijkplayer/ff_subtitle_def.h" +#import "ijksdl_gpu_metal.h" + +#if TARGET_OS_OSX #include "../mac/IJKSDLGLView.h" +#import "ijksdl_gpu_opengl_macos.h" +#import #endif -@implementation IJKSDLSubtitle - -- (void)dealloc -{ - if (_pixels) { - av_freep((void *)&_pixels); - } -} - -@end @implementation IJKOverlayAttach @@ -54,6 +47,18 @@ - (void)dealloc self.videoPicture = NULL; } self.subTexture = nil; + if (self.overlay) { + SDL_TextureOverlay_Release(&self->_overlay); + } +} + +- (id)subTexture +{ + if (self.overlay) { + return (__bridge id)self.overlay->getTexture(self.overlay); + } else { + return nil; + } } @end @@ -62,23 +67,22 @@ - (void)dealloc void *cvPixelBufferPool; int cv_format; __strong UIView *gl_view; - IJKSDLSubtitle *sub; }; -static SDL_VoutOverlay *vout_create_overlay_l(int width, int height, int src_format, int cvpixelbufferpool, SDL_Vout *vout) +static SDL_VoutOverlay *vout_create_overlay_l(int width, int height, int src_format, SDL_Vout *vout) { switch (src_format) { case AV_PIX_FMT_VIDEOTOOLBOX: return SDL_VoutFFmpeg_HW_CreateOverlay(width, height, vout); default: - return SDL_VoutFFmpeg_CreateOverlay(width, height, src_format, cvpixelbufferpool, vout); + return SDL_VoutFFmpeg_CreateOverlay(width, height, src_format, vout); } } -static SDL_VoutOverlay *vout_create_overlay_apple(int width, int height, int src_format, int cvpixelbufferpool, SDL_Vout *vout) +static SDL_VoutOverlay *vout_create_overlay(int width, int height, int src_format, SDL_Vout *vout) { SDL_LockMutex(vout->mutex); - SDL_VoutOverlay *overlay = vout_create_overlay_l(width, height, src_format, cvpixelbufferpool, vout); + SDL_VoutOverlay *overlay = vout_create_overlay_l(width, height, src_format, vout); SDL_UnlockMutex(vout->mutex); return overlay; } @@ -91,7 +95,6 @@ static void vout_free_l(SDL_Vout *vout) SDL_Vout_Opaque *opaque = vout->opaque; if (opaque) { opaque->gl_view = nil; - opaque->sub = nil; if (opaque->cvPixelBufferPool) { CVPixelBufferPoolRelease(opaque->cvPixelBufferPool); opaque->cvPixelBufferPool = NULL; @@ -113,7 +116,7 @@ static CVPixelBufferRef SDL_Overlay_getCVPixelBufferRef(SDL_VoutOverlay *overlay } } -static int vout_display_overlay_l(SDL_Vout *vout, SDL_VoutOverlay *overlay) +static int vout_display_overlay_l(SDL_Vout *vout, SDL_VoutOverlay *overlay, SDL_TextureOverlay *sub_overlay) { SDL_Vout_Opaque *opaque = vout->opaque; UIView* gl_view = opaque->gl_view; @@ -124,8 +127,9 @@ static int vout_display_overlay_l(SDL_Vout *vout, SDL_VoutOverlay *overlay) } if (!overlay) { - ALOGE("vout_display_overlay_l: NULL overlay\n"); - return -2; + IJKOverlayAttach *attach = [[IJKOverlayAttach alloc] init]; + attach.overlay = SDL_TextureOverlay_Retain(sub_overlay); + return [gl_view displayAttach:attach]; } if (overlay->w <= 0 || overlay->h <= 0) { @@ -153,8 +157,7 @@ static int vout_display_overlay_l(SDL_Vout *vout, SDL_VoutOverlay *overlay) attach.autoZRotate = overlay->auto_z_rotate_degrees; //attach.bufferW = overlay->pitches[0]; attach.videoPicture = CVPixelBufferRetain(videoPic); - attach.sub = opaque->sub; - + attach.overlay = SDL_TextureOverlay_Retain(sub_overlay); return [gl_view displayAttach:attach]; } else { ALOGE("vout_display_overlay_l: no video picture.\n"); @@ -162,131 +165,277 @@ static int vout_display_overlay_l(SDL_Vout *vout, SDL_VoutOverlay *overlay) } } -static int vout_display_overlay(SDL_Vout *vout, SDL_VoutOverlay *overlay) +static int vout_display_overlay(SDL_Vout *vout, SDL_VoutOverlay *overlay, SDL_TextureOverlay *sub_overlay) { @autoreleasepool { SDL_LockMutex(vout->mutex); - int retval = vout_display_overlay_l(vout, overlay); + int retval = vout_display_overlay_l(vout, overlay, sub_overlay); SDL_UnlockMutex(vout->mutex); return retval; } } -static void vout_update_subtitle(SDL_Vout *vout, const char *text) +SDL_Vout *SDL_VoutIos_CreateForGLES2(void) { + SDL_Vout *vout = SDL_Vout_CreateInternal(sizeof(SDL_Vout_Opaque)); + if (!vout) + return NULL; + SDL_Vout_Opaque *opaque = vout->opaque; - if (!opaque) { - return; + opaque->cv_format = -1; + vout->create_overlay = vout_create_overlay; + vout->free_l = vout_free_l; + vout->display_overlay = vout_display_overlay; + return vout; +} + +static void SDL_VoutIos_SetGLView_l(SDL_Vout *vout, UIView* view) +{ + SDL_Vout_Opaque *opaque = vout->opaque; + if (opaque->gl_view != view) { + opaque->gl_view = view; } - - opaque->sub = nil; - - if (!text || strlen(text) == 0) { - return; +} + +void SDL_VoutIos_SetGLView(SDL_Vout *vout, UIView* view) +{ + SDL_LockMutex(vout->mutex); + SDL_VoutIos_SetGLView_l(vout, view); + SDL_UnlockMutex(vout->mutex); +} + +#if TARGET_OS_OSX +@interface _IJKSDLGLTextureWrapper : NSObject + +@property(nonatomic) GLuint texture; +@property(nonatomic) int w; +@property(nonatomic) int h; + +@end + +@implementation _IJKSDLGLTextureWrapper + +- (void)dealloc +{ + if (_texture) { + glDeleteTextures(1, &_texture); + _texture = 0; } - - IJKSDLSubtitle *sub = [[IJKSDLSubtitle alloc]init]; - sub.text = [[NSString alloc] initWithUTF8String:text]; - opaque->sub = sub; } -static uint8_t* copy_pal8_to_bgra(const AVSubtitleRect* rect) +- (GLuint)texture { - const int buff_size = rect->w * rect->h * 4; /* times 4 because 4 bytes per pixel */ - uint32_t *buff = av_malloc((size_t)buff_size); - if (buff == NULL) { - ALOGE("Error allocating memory for subtitle bitmap.\n"); - return NULL; + return _texture; +} + +- (instancetype)initWith:(uint32_t)texture w:(int)w h:(int)h +{ + self = [super init]; + if (self) { + self.w = w; + self.h = h; + self.texture = texture; } - - //AV_PIX_FMT_RGB32 is handled in an endian-specific manner. An RGBA color is put together as: (A << 24) | (R << 16) | (G << 8) | B - //This is stored as BGRA on little-endian CPU architectures and ARGB on big-endian CPUs. - - uint32_t colors[256]; - - uint8_t *bgra = rect->data[1]; - if (bgra) { - for (int i = 0; i < 256; ++i) { - /* Colour conversion. */ - int idx = i * 4; /* again, 4 bytes per pixel */ - uint8_t a = bgra[idx], - r = bgra[idx + 1], - g = bgra[idx + 2], - b = bgra[idx + 3]; - colors[i] = (b << 24) | (g << 16) | (r << 8) | a; - } - } else { - bzero(colors, 256); + return self; +} + +@end + +id IJKSDL_crate_openglTextureWrapper(uint32_t texture, int w, int h) +{ + return [[_IJKSDLGLTextureWrapper alloc] initWith:texture w:w h:h]; +} +#endif + +SDL_TextureOverlay * SDL_TextureOverlay_Retain(SDL_TextureOverlay *t) +{ + if (t) { + __atomic_add_fetch(&t->refCount, 1, __ATOMIC_RELEASE); } - - for (int y = 0; y < rect->h; ++y) { - for (int x = 0; x < rect->w; ++x) { - /* 1 byte per pixel */ - int coordinate = x + y * rect->linesize[0]; - /* 32bpp color table */ - int pos = rect->data[0][coordinate]; - if (pos < 256) { - buff[x + (y * rect->w)] = colors[pos]; - } else { - printf("%d\n",pos); + return t; +} + +void SDL_TextureOverlay_Release(SDL_TextureOverlay **tp) +{ + if (tp) { + if (*tp) { + if (__atomic_add_fetch(&(*tp)->refCount, -1, __ATOMIC_RELEASE) == 0) { + (*tp)->dealloc(*tp); + free(*tp); } } + *tp = NULL; } - - return (uint8_t*)buff; } -static void vout_update_subtitle_picture(SDL_Vout *vout, const AVSubtitleRect *bmp) +void SDL_FBOOverlayFreeP(SDL_FBOOverlay **poverlay) { - SDL_Vout_Opaque *opaque = vout->opaque; - if (!opaque) { - return; + if (poverlay) { + if (*poverlay) { + (*poverlay)->dealloc(*poverlay); + free(*poverlay); + } + *poverlay = NULL; } - opaque->sub = nil; - - if (!bmp) { - return; +} + +SDL_GPU *SDL_CreateGPU_WithContext(id context) +{ +#if TARGET_OS_OSX + if ([context isKindOfClass:[NSOpenGLContext class]]) { + return SDL_CreateGPU_WithGLContext(context); + } else if (context){ + return SDL_CreateGPU_WithMTLDevice(context); } - - IJKSDLSubtitle *sub = [[IJKSDLSubtitle alloc]init]; - /// the graphic subtitles' bitmap with pixel format AV_PIX_FMT_PAL8, - /// https://ffmpeg.org/doxygen/trunk/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5 - /// need to be converted to BGRA32 before use - /// PAL8 to BGRA32, bytes per line increased by multiplied 4 - sub.w = bmp->w; - sub.h = bmp->h; - sub.pixels = copy_pal8_to_bgra(bmp); - opaque->sub = sub; +#else + return SDL_CreateGPU_WithMTLDevice(context); +#endif + return NULL; } -SDL_Vout *SDL_VoutIos_CreateForGLES2(void) +void SDL_GPUFreeP(SDL_GPU **pgpu) { - SDL_Vout *vout = SDL_Vout_CreateInternal(sizeof(SDL_Vout_Opaque)); - if (!vout) - return NULL; + if (pgpu) { + if (*pgpu) { + (*pgpu)->dealloc(*pgpu); + free(*pgpu); + } + *pgpu = NULL; + } +} - SDL_Vout_Opaque *opaque = vout->opaque; - opaque->cv_format = -1; +#if TARGET_OS_OSX +#pragma mark - save image for debug ass + +static CGContextRef _CreateCGBitmapContext(size_t w, size_t h, size_t bpc, size_t bpp, size_t bpr, uint32_t bmi) +{ + assert(bpp != 24); + /* + AV_PIX_FMT_RGB24 bpp is 24! not supported! + Crash: + 2020-06-06 00:08:20.245208+0800 FFmpegTutorial[23649:2335631] [Unknown process name] CGBitmapContextCreate: unsupported parameter combination: set CGBITMAP_CONTEXT_LOG_ERRORS environmental variable to see the details + 2020-06-06 00:08:20.245417+0800 FFmpegTutorial[23649:2335631] [Unknown process name] CGBitmapContextCreateImage: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable. + */ + //Update: Since 10.8, CGColorSpaceCreateDeviceRGB is equivalent to sRGB, and is closer to option 2) + //CGColorSpaceCreateWithName(kCGColorSpaceSRGB) + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef bitmapContext = CGBitmapContextCreate( + NULL, + w, + h, + bpc, + bpr, + colorSpace, + bmi + ); - vout->create_overlay_apple = vout_create_overlay_apple; - vout->free_l = vout_free_l; - vout->display_overlay = vout_display_overlay; - vout->update_subtitle = vout_update_subtitle; - vout->update_subtitle_picture = vout_update_subtitle_picture; - return vout; + CGColorSpaceRelease(colorSpace); + return bitmapContext; } -static void SDL_VoutIos_SetGLView_l(SDL_Vout *vout, UIView* view) +static NSError * mr_mkdirP(NSString *aDir) { - SDL_Vout_Opaque *opaque = vout->opaque; - if (opaque->gl_view != view) { - opaque->gl_view = view; + BOOL isDirectory = NO; + if ([[NSFileManager defaultManager] fileExistsAtPath:aDir isDirectory:&isDirectory]) { + if (isDirectory) { + return nil; + } else { + //remove the file + [[NSFileManager defaultManager] removeItemAtPath:aDir error:NULL]; + } } + //aDir is not exist + NSError *err = nil; + [[NSFileManager defaultManager] createDirectoryAtPath:aDir withIntermediateDirectories:YES attributes:nil error:&err]; + return err; } -void SDL_VoutIos_SetGLView(SDL_Vout *vout, UIView* view) +static NSString * mr_DirWithType(NSSearchPathDirectory directory,NSArray*pathArr) { - SDL_LockMutex(vout->mutex); - SDL_VoutIos_SetGLView_l(vout, view); - SDL_UnlockMutex(vout->mutex); + NSString *directoryDir = [NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES) firstObject]; + NSString *aDir = directoryDir; + for (NSString *dir in pathArr) { + aDir = [aDir stringByAppendingPathComponent:dir]; + } + if (mr_mkdirP(aDir)) { + return nil; + } + return aDir; } + +static BOOL saveImageToFile(CGImageRef img,NSString *imgPath) +{ + CFStringRef imageUTType = NULL; + NSString *fileType = [[imgPath pathExtension] lowercaseString]; + if ([fileType isEqualToString:@"jpg"] || [fileType isEqualToString:@"jpeg"]) { + imageUTType = kUTTypeJPEG; + } else if ([fileType isEqualToString:@"png"]) { + imageUTType = kUTTypePNG; + } else if ([fileType isEqualToString:@"tiff"]) { + imageUTType = kUTTypeTIFF; + } else if ([fileType isEqualToString:@"bmp"]) { + imageUTType = kUTTypeBMP; + } else if ([fileType isEqualToString:@"gif"]) { + imageUTType = kUTTypeGIF; + } else if ([fileType isEqualToString:@"pdf"]) { + imageUTType = kUTTypePDF; + } + + if (imageUTType == NULL) { + imageUTType = kUTTypePNG; + } + + CFStringRef key = kCGImageDestinationLossyCompressionQuality; + CFStringRef value = CFSTR("0.5"); + const void * keys[] = {key}; + const void * values[] = {value}; + CFDictionaryRef opts = CFDictionaryCreate(CFAllocatorGetDefault(), keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + NSURL *fileUrl = [NSURL fileURLWithPath:imgPath]; + CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef) fileUrl, imageUTType, 1, opts); + CFRelease(opts); + + if (destination) { + CGImageDestinationAddImage(destination, img, NULL); + CGImageDestinationFinalize(destination); + CFRelease(destination); + return YES; + } else { + return NO; + } +} + +void SaveIMGToFile(uint8_t *data,int width,int height,IMG_FORMAT format, char *tag, int pts) +{ + const GLint bytesPerRow = width * 4; + + uint32_t bmi; + if (format == IMG_FORMAT_RGBA) { + bmi = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast; + } else { + bmi = kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst; + } + CGContextRef ctx = _CreateCGBitmapContext(width, height, 8, 32, bytesPerRow, bmi); + if (ctx) { + void * bitmapData = CGBitmapContextGetData(ctx); + if (bitmapData) { + memcpy(bitmapData, data, bytesPerRow * height); + CGImageRef img = CGBitmapContextCreateImage(ctx); + if (img) { + NSString *dir = mr_DirWithType(NSPicturesDirectory, @[@"ijkplayer"]); + if (!tag) { + tag = ""; + } + if (pts == -1) { + pts = (int)CFAbsoluteTimeGetCurrent(); + } + NSString *fileName = [NSString stringWithFormat:@"%s-%d.png",tag,pts]; + NSString *filePath = [dir stringByAppendingPathComponent:fileName]; + NSLog(@"save img:%@",filePath); + saveImageToFile(img, filePath); + CFRelease(img); + } + } + CGContextRelease(ctx); + } +} +#endif diff --git a/ijkmedia/ijksdl/apple/ijksdl_vout_overlay_ffmpeg.h b/ijkmedia/ijksdl/apple/ijksdl_vout_overlay_ffmpeg.h index 3370f128c4..3e51620c49 100644 --- a/ijkmedia/ijksdl/apple/ijksdl_vout_overlay_ffmpeg.h +++ b/ijkmedia/ijksdl/apple/ijksdl_vout_overlay_ffmpeg.h @@ -28,12 +28,11 @@ #include "../ijksdl_stdinc.h" #include "../ijksdl_vout.h" #include "ijksdl_inc_ffmpeg.h" -#include "internal.h" #import #import -SDL_VoutOverlay *SDL_VoutFFmpeg_CreateOverlay(int width, int height, int src_format, int cvpixelbufferpool, SDL_Vout *vout); +SDL_VoutOverlay *SDL_VoutFFmpeg_CreateOverlay(int width, int height, int src_format, SDL_Vout *vout); CVPixelBufferRef SDL_VoutFFmpeg_GetCVPixelBufferRef(SDL_VoutOverlay *overlay); #endif diff --git a/ijkmedia/ijksdl/apple/ijksdl_vout_overlay_ffmpeg.m b/ijkmedia/ijksdl/apple/ijksdl_vout_overlay_ffmpeg.m index 9d5221f9a9..ce94feafad 100644 --- a/ijkmedia/ijksdl/apple/ijksdl_vout_overlay_ffmpeg.m +++ b/ijkmedia/ijksdl/apple/ijksdl_vout_overlay_ffmpeg.m @@ -369,7 +369,7 @@ static int func_fill_avframe_to_cvpixelbuffer(SDL_VoutOverlay *overlay, const AV int cv_format; }; -SDL_VoutOverlay *SDL_VoutFFmpeg_CreateOverlay(int width, int height,int src_format, int cvpixelbufferpool, SDL_Vout *display) +SDL_VoutOverlay *SDL_VoutFFmpeg_CreateOverlay(int width, int height,int src_format, SDL_Vout *display) { enum AVPixelFormat const format = src_format; if(format == AV_PIX_FMT_NONE) { @@ -400,7 +400,7 @@ static int func_fill_avframe_to_cvpixelbuffer(SDL_VoutOverlay *overlay, const AV overlay->func_fill_frame = func_fill_avframe_to_cvpixelbuffer; SDL_Vout_Opaque * voutOpaque = display->opaque; - if (cvpixelbufferpool && !voutOpaque->cvPixelBufferPool) { + if (display->cvpixelbufferpool && !voutOpaque->cvPixelBufferPool) { CVPixelBufferPoolRef cvPixelBufferPool = NULL; createCVPixelBufferPoolFromAVFrame(&cvPixelBufferPool, width, height, format); voutOpaque->cvPixelBufferPool = cvPixelBufferPool; diff --git a/ijkmedia/ijksdl/gles2/fsh/apple_common.fsh.c b/ijkmedia/ijksdl/gles2/fsh/apple_common.fsh.c index f8aa73b6d5..c1b0659f7b 100644 --- a/ijkmedia/ijksdl/gles2/fsh/apple_common.fsh.c +++ b/ijkmedia/ijksdl/gles2/fsh/apple_common.fsh.c @@ -114,11 +114,13 @@ static const char g_shader_hdr[] = IJK_GLES_STRING( void main() { if (isSubtitle == 1) { + vec4 subColor; #if TARGET_OS_OSX - fragColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); + subColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); #else - fragColor = texture2DRect(subSampler, vv2_Texcoord); + subColor = texture2DRect(subSampler, vv2_Texcoord); #endif + fragColor = subColor.bgra; return; } // 0、先把 [0.0,1.0] 范围的YUV 处理为 [0.0,1.0] 范围的RGB @@ -208,11 +210,13 @@ static const char g_shader_nv12[] = IJK_GLES_STRING( void main() { if (isSubtitle == 1) { -#if TARGET_OS_OSX - fragColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); -#else - fragColor = texture2DRect(subSampler, vv2_Texcoord); -#endif + vec4 subColor; + #if TARGET_OS_OSX + subColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); + #else + subColor = texture2DRect(subSampler, vv2_Texcoord); + #endif + fragColor = subColor.bgra; return; } #if TARGET_OS_OSX @@ -256,11 +260,13 @@ static const char g_shader_rect_bgrx_1[] = IJK_GLES_STRING( void main() { if (isSubtitle == 1) { + vec4 subColor; #if TARGET_OS_OSX - fragColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); + subColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); #else - fragColor = texture2DRect(subSampler, vv2_Texcoord); + subColor = texture2DRect(subSampler, vv2_Texcoord); #endif + fragColor = subColor.bgra; return; } #if TARGET_OS_OSX @@ -288,7 +294,8 @@ static const char g_shader_rect_uyvy_legacy_1[] = IJK_GLES_STRING( void main() { if (isSubtitle == 1) { - fragColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); + vec4 subColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); + fragColor = subColor.bgra; return; } vec2 recTexCoord0 = vv2_Texcoord * textureDimension0; @@ -314,11 +321,13 @@ static const char g_shader_rect_xrgb_1[] = IJK_GLES_STRING( void main() { if (isSubtitle == 1) { + vec4 subColor; #if TARGET_OS_OSX - fragColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); + subColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); #else - fragColor = texture2DRect(subSampler, vv2_Texcoord); + subColor = texture2DRect(subSampler, vv2_Texcoord); #endif + fragColor = subColor.bgra; return; } #if TARGET_OS_OSX @@ -356,11 +365,13 @@ static const char g_shader_rect_3[] = IJK_GLES_STRING( void main() { if (isSubtitle == 1) { + vec4 subColor; #if TARGET_OS_OSX - fragColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); + subColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); #else - fragColor = texture2DRect(subSampler, vv2_Texcoord); + subColor = texture2DRect(subSampler, vv2_Texcoord); #endif + fragColor = subColor.bgra; return; } #if TARGET_OS_OSX @@ -407,11 +418,13 @@ static const char g_shader_rect_uyvy_1[] = IJK_GLES_STRING( void main() { if (isSubtitle == 1) { + vec4 subColor; #if TARGET_OS_OSX - fragColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); + subColor = texture2DRect(subSampler, vv2_Texcoord * subTextureDimension); #else - fragColor = texture2DRect(subSampler, vv2_Texcoord); + subColor = texture2DRect(subSampler, vv2_Texcoord); #endif + fragColor = subColor.bgra; return; } vec2 recTexCoord0 = vv2_Texcoord * textureDimension0; diff --git a/ijkmedia/ijksdl/gles2/internal.h b/ijkmedia/ijksdl/gles2/internal.h index 995d7dfe12..49ea0e2a46 100644 --- a/ijkmedia/ijksdl/gles2/internal.h +++ b/ijkmedia/ijksdl/gles2/internal.h @@ -82,8 +82,6 @@ typedef struct IJK_GLES2_Renderer GLuint program; - GLuint vertex_shader; - GLuint fragment_shader; GLuint plane_textures[IJK_GLES2_MAX_PLANE]; GLint av4_position; @@ -148,7 +146,6 @@ typedef struct IJK_GLES2_Renderer //for rotate int rotate_type;//x=1;y=2;z=3 int rotate_degrees; - float subtitle_bottom_margin; GLfloat rgb_adjustment[3]; } IJK_GLES2_Renderer; diff --git a/ijkmedia/ijksdl/gles2/renderer.c b/ijkmedia/ijksdl/gles2/renderer.c index 381bcc2c4b..6797674ac7 100644 --- a/ijkmedia/ijksdl/gles2/renderer.c +++ b/ijkmedia/ijksdl/gles2/renderer.c @@ -67,16 +67,8 @@ void IJK_GLES2_Renderer_reset(IJK_GLES2_Renderer *renderer) { if (!renderer) return; - - if (renderer->vertex_shader) - glDeleteShader(renderer->vertex_shader); - if (renderer->fragment_shader) - glDeleteShader(renderer->fragment_shader); if (renderer->program) glDeleteProgram(renderer->program); - - renderer->vertex_shader = 0; - renderer->fragment_shader = 0; renderer->program = 0; for (int i = 0; i < IJK_GLES2_MAX_PLANE; ++i) { @@ -85,7 +77,6 @@ void IJK_GLES2_Renderer_reset(IJK_GLES2_Renderer *renderer) renderer->plane_textures[i] = 0; } } - glDeleteBuffers(1, &renderer->vbo); glDeleteVertexArrays(1, &renderer->vao); } @@ -94,21 +85,12 @@ void IJK_GLES2_Renderer_free(IJK_GLES2_Renderer *renderer) { if (!renderer) return; - + //delete opengl shader and buffers + IJK_GLES2_Renderer_reset(renderer); + if (renderer->func_destroy) renderer->func_destroy(renderer); - -#if 0 - if (renderer->vertex_shader) ALOGW("[GLES2] renderer: vertex_shader not deleted.\n"); - if (renderer->fragment_shader) ALOGW("[GLES2] renderer: fragment_shader not deleted.\n"); - if (renderer->program) ALOGW("[GLES2] renderer: program not deleted.\n"); - - for (int i = 0; i < IJK_GLES2_MAX_PLANE; ++i) { - if (renderer->plane_textures[i]) - ALOGW("[GLES2] renderer: plane texture[%d] not deleted.\n", i); - } -#endif - + IJK_GLES2_checkError("renderer free"); free(renderer); } @@ -160,8 +142,12 @@ IJK_GLES2_Renderer *IJK_GLES2_Renderer_create_base(const char *fragment_shader_s if (!renderer) goto fail; - renderer->vertex_shader = vertex_shader; - renderer->fragment_shader = fragment_shader; + if (vertex_shader) + glDeleteShader(vertex_shader); + if (fragment_shader) + glDeleteShader(fragment_shader); + IJK_GLES2_checkError("glDeleteShader"); + renderer->program = program; renderer->av4_position = glGetAttribLocation(renderer->program, "av4_Position"); IJK_GLES2_checkError_TRACE("glGetAttribLocation(av4_Position)"); @@ -173,7 +159,7 @@ IJK_GLES2_Renderer *IJK_GLES2_Renderer_create_base(const char *fragment_shader_s return renderer; fail: - + if (renderer && renderer->program) IJK_GLES2_printProgramInfo(renderer->program); @@ -491,11 +477,6 @@ void IJK_GLES2_Renderer_updateRotate(IJK_GLES2_Renderer *renderer,int type,int d } } -void IJK_GLES2_Renderer_updateSubtitleBottomMargin(IJK_GLES2_Renderer *renderer,float value) -{ - renderer->subtitle_bottom_margin = value; -} - void IJK_GLES2_Renderer_updateAutoZRotate(IJK_GLES2_Renderer *renderer,int degrees) { if (renderer->auto_z_rotate_degrees != degrees) { @@ -559,6 +540,9 @@ static void IJK_GLES2_Renderer_Upload_Vbo_Data(IJK_GLES2_Renderer *renderer) renderer->texcoords[6],renderer->texcoords[7], }; + // 更新顶点数据 + glBindVertexArray(renderer->vao); + // 绑定顶点缓存对象到当前的顶点位置,之后对GL_ARRAY_BUFFER的操作即是对_VBO的操作 glBindBuffer(GL_ARRAY_BUFFER, renderer->vbo); // 将CPU数据发送到GPU,数据类型GL_ARRAY_BUFFER @@ -627,7 +611,25 @@ static void IJK_GLES2_updateMVP_ifNeed(IJK_GLES2_Renderer *renderer) /* * Per-Renderer routine */ -GLboolean IJK_GLES2_Renderer_use(IJK_GLES2_Renderer *renderer) +GLboolean IJK_GLES2_Renderer_init(IJK_GLES2_Renderer *renderer) +{ + if (IJK_GLES2_Renderer_useProgram(renderer)) { + renderer->rgb_adjustment[0] = 1.0; + renderer->rgb_adjustment[1] = 1.0; + renderer->rgb_adjustment[2] = 1.0; + renderer->mvp_changed = 1; + renderer->rgb_adjust_changed = 1; + + IJK_GLES2_Renderer_TexCoords_reset(renderer); + IJK_GLES2_Renderer_Vertices_reset(renderer); + IJK_GLES2_Renderer_Upload_Vbo_Data(renderer); + IJK_GLES2_updateMVP_ifNeed(renderer); + return GL_TRUE; + } + return GL_FALSE; +} + +GLboolean IJK_GLES2_Renderer_useProgram(IJK_GLES2_Renderer *renderer) { if (!renderer) return GL_FALSE; @@ -635,17 +637,6 @@ GLboolean IJK_GLES2_Renderer_use(IJK_GLES2_Renderer *renderer) assert(renderer->func_use); if (!renderer->func_use(renderer)) return GL_FALSE; - - renderer->rgb_adjustment[0] = 1.0; - renderer->rgb_adjustment[1] = 1.0; - renderer->rgb_adjustment[2] = 1.0; - renderer->mvp_changed = 1; - renderer->rgb_adjust_changed = 1; - - IJK_GLES2_Renderer_TexCoords_reset(renderer); - IJK_GLES2_Renderer_Vertices_reset(renderer); - IJK_GLES2_Renderer_Upload_Vbo_Data(renderer); - IJK_GLES2_updateMVP_ifNeed(renderer); return GL_TRUE; } @@ -801,7 +792,7 @@ GLboolean IJK_GLES2_Renderer_updateVertex2(IJK_GLES2_Renderer *renderer, IJK_GLES2_updateMVP_ifNeed(renderer); IJK_GLES2_updateRGB_adjust_ifNeed(renderer); glBindVertexArray(renderer->vao); IJK_GLES2_checkError_TRACE("glBindVertexArray"); - + IJK_GLES2_checkError_TRACE("updateVertex2"); return GL_TRUE; } @@ -897,20 +888,26 @@ GLboolean IJK_GLES2_Renderer_uploadSubtitleTexture(IJK_GLES2_Renderer *renderer, void IJK_GLES2_Renderer_updateSubtitleVertex(IJK_GLES2_Renderer *renderer, float width, float height) { glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - //字幕图片在纹理坐标系上的尺寸 - float ratioW = 1.0 * width / renderer->layer_width; - float ratioH = 1.0 * height / renderer->layer_height; - - float leftX = 0 - ratioW; - float rightX = 0 + ratioW; - //距离底部0.1,实际是 (0.1 * layer_height)px; [-1,1] - float margin = (renderer->subtitle_bottom_margin - 1) * 2 + 1; - margin = FFMAX(margin, -1.0); - margin = FFMIN(margin, 1.0 - 2 * ratioH); - float bottomY = margin; - float topY = bottomY + 2 * ratioH; + //ass字幕已经做了预乘,所以这里选择 GL_ONE,而不是 GL_SRC_ALPHA + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + float wRatio = renderer->layer_width / width; + float hRatio = renderer->layer_height / height; + + CGRect subRect; + //aspect fit + if (wRatio < hRatio) { + float nH = (height * wRatio / renderer->layer_height); + subRect = CGRectMake(-1, -nH, 2.0, 2.0 * nH); + } else { + float nW = (width * hRatio / renderer->layer_width); + subRect = CGRectMake(-nW, -1, 2.0 * nW, 2.0); + } + + float leftX = subRect.origin.x; + float rightX = leftX + subRect.size.width; + float bottomY = subRect.origin.y; + float topY = bottomY + subRect.size.height; //左下 renderer->vertices[0] = leftX; diff --git a/ijkmedia/ijksdl/gles2/renderer_apple.m b/ijkmedia/ijksdl/gles2/renderer_apple.m index 88fd813d67..43e4417313 100644 --- a/ijkmedia/ijksdl/gles2/renderer_apple.m +++ b/ijkmedia/ijksdl/gles2/renderer_apple.m @@ -61,7 +61,7 @@ - (BOOL)texImageIOSurface:(IOSurfaceRef)arg1 target:(unsigned long long)arg2 int static GLboolean use(IJK_GLES2_Renderer *renderer) { - ALOGI("use common vtb render\n"); + //ALOGI("use common vtb render\n"); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); //glEnable(GL_TEXTURE_TARGET); glUseProgram(renderer->program); IJK_GLES2_checkError_TRACE("glUseProgram"); @@ -232,17 +232,20 @@ static GLboolean upload_Texture(IJK_GLES2_Renderer *renderer, void *picture) if (!opaque) { return GL_FALSE; } - + IJK_GLES2_checkError_TRACE("transferFunUM begin"); if (renderer->colorMatrix != YUV_2_RGB_Color_Matrix_None) { glUniformMatrix3fv(renderer->um3_color_conversion, 1, GL_FALSE, IJK_GLES2_getColorMatrix(renderer->colorMatrix)); + IJK_GLES2_checkError_TRACE("transferFunUM 1"); } if (renderer->fullRangeUM != -1) { glUniform1i(renderer->fullRangeUM, renderer->isFullRange); + IJK_GLES2_checkError_TRACE("transferFunUM 2"); } if (renderer->transferFunUM != -1) { glUniform1i(renderer->transferFunUM, renderer->transferFun); + IJK_GLES2_checkError_TRACE("transferFunUM 3"); } if (renderer->hdrAnimationUM != -1) { @@ -284,19 +287,23 @@ static GLboolean uploadSubtitle(IJK_GLES2_Renderer *renderer, int texture, int w if (!opaque) { return GL_FALSE; } - + GLenum GLTarget = GL_TEXTURE_TARGET; glActiveTexture(GL_TEXTURE0); glBindTexture(GLTarget, texture); + IJK_GLES2_checkError_TRACE("uploadSubtitle1"); //设置采样器位置,和纹理单元对应 glUniform1i(renderer->subSampler, 0); + IJK_GLES2_checkError_TRACE("uploadSubtitle2"); #if TARGET_OS_OSX glUniform2f(renderer->opaque->subTextureDimension, w, h); #endif + IJK_GLES2_checkError_TRACE("uploadSubtitle3"); glTexParameteri(GLTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GLTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GLTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GLTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + return true; } diff --git a/ijkmedia/ijksdl/gles2/renderer_pixfmt.h b/ijkmedia/ijksdl/gles2/renderer_pixfmt.h index 708be0bc91..4e2a8b630f 100644 --- a/ijkmedia/ijksdl/gles2/renderer_pixfmt.h +++ b/ijkmedia/ijksdl/gles2/renderer_pixfmt.h @@ -233,9 +233,9 @@ static struct vt_format vt_formats[] = { { GL_RED, GL_UNSIGNED_BYTE, GL_RED }, { GL_RED, GL_UNSIGNED_BYTE, GL_RED } #else - { GL_RED_EXT, GL_UNSIGNED_BYTE, GL_RED_EXT }, - { GL_RED_EXT, GL_UNSIGNED_BYTE, GL_RED_EXT }, - { GL_RED_EXT, GL_UNSIGNED_BYTE, GL_RED_EXT } + { GL_RED, GL_UNSIGNED_BYTE, GL_RED }, + { GL_RED, GL_UNSIGNED_BYTE, GL_RED }, + { GL_RED, GL_UNSIGNED_BYTE, GL_RED } #endif } }, @@ -248,9 +248,9 @@ static struct vt_format vt_formats[] = { { GL_RED, GL_UNSIGNED_BYTE, GL_RED }, { GL_RED, GL_UNSIGNED_BYTE, GL_RED } #else - { GL_RED_EXT, GL_UNSIGNED_BYTE, GL_RED_EXT }, - { GL_RED_EXT, GL_UNSIGNED_BYTE, GL_RED_EXT }, - { GL_RED_EXT, GL_UNSIGNED_BYTE, GL_RED_EXT } + { GL_RED, GL_UNSIGNED_BYTE, GL_RED }, + { GL_RED, GL_UNSIGNED_BYTE, GL_RED }, + { GL_RED, GL_UNSIGNED_BYTE, GL_RED } #endif } }, diff --git a/ijkmedia/ijksdl/ijksdl_egl.c b/ijkmedia/ijksdl/ijksdl_egl.c index f2c995ab1a..48b6db46cd 100644 --- a/ijkmedia/ijksdl/ijksdl_egl.c +++ b/ijkmedia/ijksdl/ijksdl_egl.c @@ -19,8 +19,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef __APPLE__ - #include "ijksdl_egl.h" #include @@ -385,5 +383,3 @@ IJK_EGL *IJK_EGL_create() return egl; } - -#endif diff --git a/ijkmedia/ijksdl/ijksdl_gles2.h b/ijkmedia/ijksdl/ijksdl_gles2.h index b9b0487861..9605c13d47 100644 --- a/ijkmedia/ijksdl/ijksdl_gles2.h +++ b/ijkmedia/ijksdl/ijksdl_gles2.h @@ -29,9 +29,6 @@ #include #include #include - #else - #include - #include #endif /* TARGET_OS_OSX */ #else #include @@ -77,7 +74,9 @@ void IJK_GLES2_Renderer_freeP(IJK_GLES2_Renderer **renderer); GLboolean IJK_GLES2_Renderer_isValid(IJK_GLES2_Renderer *renderer); GLboolean IJK_GLES2_Renderer_isFormat(IJK_GLES2_Renderer *renderer, int format); -GLboolean IJK_GLES2_Renderer_use(IJK_GLES2_Renderer *renderer); +//call once +GLboolean IJK_GLES2_Renderer_init(IJK_GLES2_Renderer *renderer); +GLboolean IJK_GLES2_Renderer_useProgram(IJK_GLES2_Renderer *renderer); void IJK_GLES2_Renderer_updateColorConversion(IJK_GLES2_Renderer *renderer, float brightness, float satutaion, float contrast); GLboolean IJK_GLES2_Renderer_updateVertex(IJK_GLES2_Renderer *renderer, SDL_VoutOverlay *overlay); @@ -101,7 +100,6 @@ void IJK_GLES2_Renderer_endDrawSubtitle(IJK_GLES2_Renderer *renderer); GLboolean IJK_GLES2_Renderer_setGravity(IJK_GLES2_Renderer *renderer, int gravity, GLsizei view_width, GLsizei view_height); void IJK_GLES2_Renderer_updateRotate(IJK_GLES2_Renderer *renderer, int type, int degrees); -void IJK_GLES2_Renderer_updateSubtitleBottomMargin(IJK_GLES2_Renderer *renderer, float value); void IJK_GLES2_Renderer_updateAutoZRotate(IJK_GLES2_Renderer *renderer, int degrees); void IJK_GLES2_Renderer_updateUserDefinedDAR(IJK_GLES2_Renderer *renderer, float ratio); int IJK_GLES2_Renderer_isZRotate90oddMultiple(IJK_GLES2_Renderer *renderer); diff --git a/ijkmedia/ijksdl/ijksdl_gpu.h b/ijkmedia/ijksdl/ijksdl_gpu.h new file mode 100644 index 0000000000..5c74a54677 --- /dev/null +++ b/ijkmedia/ijksdl/ijksdl_gpu.h @@ -0,0 +1,70 @@ +// +// ijksdl_gpu.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/3/26. +// + +#ifndef ijksdl_gpu_h +#define ijksdl_gpu_h + +#include "ijksdl_rectangle.h" + +typedef struct SDL_TextureOverlay SDL_TextureOverlay; +typedef struct SDL_TextureOverlay { + void *opaque; + int w; + int h; + float scale; + SDL_Rectangle dirtyRect; + int changed; + int refCount; + void (*replaceRegion)(SDL_TextureOverlay *overlay, SDL_Rectangle r, void *pixels); + void*(*getTexture)(SDL_TextureOverlay *overlay); + void (*clearDirtyRect)(SDL_TextureOverlay *overlay); + void (*dealloc)(SDL_TextureOverlay *overlay); +} SDL_TextureOverlay; + +SDL_TextureOverlay * SDL_TextureOverlay_Retain(SDL_TextureOverlay *t); +void SDL_TextureOverlay_Release(SDL_TextureOverlay **tp); + +typedef struct SDL_GPU SDL_GPU; + +typedef struct SDL_FBOOverlay SDL_FBOOverlay; +typedef struct SDL_FBOOverlay { + void *opaque; + int w; + int h; + void (*clear)(SDL_FBOOverlay *overlay); + void (*beginDraw)(SDL_GPU *gpu, SDL_FBOOverlay *overlay, int ass); + void (*drawTexture)(SDL_GPU *gpu, SDL_FBOOverlay *foverlay, SDL_TextureOverlay *toverlay, SDL_Rectangle frame); + void (*endDraw)(SDL_GPU *gpu, SDL_FBOOverlay *overlay); + SDL_TextureOverlay *(*getTexture)(SDL_FBOOverlay *overlay); + void (*dealloc)(SDL_FBOOverlay *overlay); +} SDL_FBOOverlay; + +void SDL_FBOOverlayFreeP(SDL_FBOOverlay **poverlay); + +typedef enum : int { + SDL_TEXTURE_FMT_BRGA, + SDL_TEXTURE_FMT_A8 +} SDL_TEXTURE_FMT; + +typedef struct SDL_GPU { + void *opaque; + SDL_TextureOverlay *(*createTexture)(SDL_GPU *gpu, int w, int h, SDL_TEXTURE_FMT fmt); + SDL_FBOOverlay *(*createFBO)(SDL_GPU *gpu, int w, int h); + void (*dealloc)(SDL_GPU *gpu); +} SDL_GPU; + +void SDL_GPUFreeP(SDL_GPU **pgpu); + +typedef enum : int { + IMG_FORMAT_RGBA, + IMG_FORMAT_BGRA, +} IMG_FORMAT; + +void SaveIMGToFile(uint8_t *data,int width,int height,IMG_FORMAT format, char *tag, int pts); + + +#endif /* ijksdl_gpu_h */ diff --git a/ijkmedia/ijksdl/ijksdl_rectangle.c b/ijkmedia/ijksdl/ijksdl_rectangle.c new file mode 100644 index 0000000000..2ae4069dbb --- /dev/null +++ b/ijkmedia/ijksdl/ijksdl_rectangle.c @@ -0,0 +1,51 @@ +// +// ijksdl_rectangle.c +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/3/27. +// + +#include "ijksdl_rectangle.h" +#include + +int isZeroRectangle(SDL_Rectangle rect) +{ + if (rect.w == 0 && rect.h == 0) { + return 1; + } + return 0; +} + +SDL_Rectangle SDL_union_rectangle(SDL_Rectangle rect1, SDL_Rectangle rect2) { + + if (isZeroRectangle(rect1)) { + if (isZeroRectangle(rect2)) { + return (SDL_Rectangle){0,0,0,0}; + } else { + return rect2; + } + } else if (isZeroRectangle(rect2)) { + return rect1; + } + + SDL_Rectangle result; + + // 计算新矩形的左上角坐标(取两个矩形中最小的 x 和 y 坐标) + result.x = (rect1.x < rect2.x) ? rect1.x : rect2.x; + result.y = (rect1.y < rect2.y) ? rect1.y : rect2.y; + + // 计算新矩形的右下角坐标(取两个矩形中最大的 x 和 y 坐标) + int x1 = rect1.x + rect1.w; + int y1 = rect1.y + rect1.h; + + int x2 = rect2.x + rect2.w; + int y2 = rect2.y + rect2.h; + + result.w = ((x1 > x2) ? x1 : x2) - result.x; + result.h = ((y1 > y2) ? y1 : y2) - result.y; + + assert(rect2.stride/rect2.w == rect1.stride/rect1.w); + + result.stride = result.w * rect2.stride/rect2.w; + return result; +} diff --git a/ijkmedia/ijksdl/ijksdl_rectangle.h b/ijkmedia/ijksdl/ijksdl_rectangle.h new file mode 100644 index 0000000000..bad8d02458 --- /dev/null +++ b/ijkmedia/ijksdl/ijksdl_rectangle.h @@ -0,0 +1,26 @@ +// +// ijksdl_rectangle.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/3/27. +// + +#ifndef ijksdl_rectangle_h +#define ijksdl_rectangle_h + +#include + +// 定义矩形结构体 +typedef struct SDL_Rectangle{ + int x, y; // 左上角坐标 + int w, h; // + int stride; +} SDL_Rectangle; + +int isZeroRectangle(SDL_Rectangle rect); +// 计算两个矩形的并集 +SDL_Rectangle SDL_union_rectangle(SDL_Rectangle rect1, SDL_Rectangle rect2); + +#define SDL_Zero_Rectangle (SDL_Rectangle){0,0,0,0} + +#endif /* ijksdl_rectangle_h */ diff --git a/ijkmedia/ijksdl/ijksdl_vout.c b/ijkmedia/ijksdl/ijksdl_vout.c index e54101f115..4be600457c 100644 --- a/ijkmedia/ijksdl/ijksdl_vout.c +++ b/ijkmedia/ijksdl/ijksdl_vout.c @@ -72,24 +72,15 @@ void SDL_VoutFreeP(SDL_Vout **pvout) *pvout = NULL; } -int SDL_VoutDisplayYUVOverlay(SDL_Vout *vout, SDL_VoutOverlay *overlay) +int SDL_VoutDisplayYUVOverlay(SDL_Vout *vout, SDL_VoutOverlay *overlay, SDL_TextureOverlay *sub_overlay) { - if (vout && overlay && vout->display_overlay) - return vout->display_overlay(vout, overlay); + if (vout && vout->display_overlay) + return vout->display_overlay(vout, overlay, sub_overlay); return -1; } -int SDL_VoutSetOverlayFormat(SDL_Vout *vout, Uint32 overlay_format) -{ - if (!vout) - return -1; - - vout->overlay_format = overlay_format; - return 0; -} - -int SDL_VoutConvertFrame(SDL_Vout *vout, int dst_format,const AVFrame *inFrame, const AVFrame **outFrame) +int SDL_VoutConvertFrame(SDL_Vout *vout, int dst_format, const AVFrame *inFrame, const AVFrame **outFrame) { if (!vout) { return -1; @@ -175,15 +166,6 @@ int SDL_VoutConvertFrame(SDL_Vout *vout, int dst_format,const AVFrame *inFrame, return r; } -#ifdef __APPLE__ -SDL_VoutOverlay *SDL_Vout_CreateOverlay_Apple(int width, int height, int src_format, int cvpixelbufferpool, SDL_Vout *vout) -{ - if (vout && vout->create_overlay_apple) - return vout->create_overlay_apple(width, height, src_format, cvpixelbufferpool, vout); - - return NULL; -} -#else SDL_VoutOverlay *SDL_Vout_CreateOverlay(int width, int height, int src_format, SDL_Vout *vout) { if (vout && vout->create_overlay) @@ -191,7 +173,6 @@ SDL_VoutOverlay *SDL_Vout_CreateOverlay(int width, int height, int src_format, S return NULL; } -#endif int SDL_VoutLockYUVOverlay(SDL_VoutOverlay *overlay) { diff --git a/ijkmedia/ijksdl/ijksdl_vout.h b/ijkmedia/ijksdl/ijksdl_vout.h index 7403224113..c74f3ace2f 100644 --- a/ijkmedia/ijksdl/ijksdl_vout.h +++ b/ijkmedia/ijksdl/ijksdl_vout.h @@ -62,39 +62,29 @@ struct SDL_VoutOverlay { typedef struct SDL_Vout_Opaque SDL_Vout_Opaque; typedef struct SDL_Vout SDL_Vout; +typedef struct SDL_TextureOverlay SDL_TextureOverlay; + struct SDL_Vout { SDL_mutex *mutex; SDL_Class *opaque_class; SDL_Vout_Opaque *opaque; -#ifdef __APPLE__ - SDL_VoutOverlay *(*create_overlay_apple)(int width, int height, int src_format, int cvpixelbufferpool, SDL_Vout *vout); -#else SDL_VoutOverlay *(*create_overlay)(int width, int height, int frame_format, SDL_Vout *vout); -#endif void (*free_l)(SDL_Vout *vout); - int (*display_overlay)(SDL_Vout *vout, SDL_VoutOverlay *overlay); - void (*update_subtitle)(SDL_Vout *vout, const char *text); - void (*update_subtitle_picture)(SDL_Vout *vout, const AVSubtitleRect *rect); - + int (*display_overlay)(SDL_Vout *vout, SDL_VoutOverlay *overlay, SDL_TextureOverlay *sub_overlay); Uint32 overlay_format; int z_rotate_degrees; //convert image void *image_converter; + int cvpixelbufferpool; }; void SDL_VoutFree(SDL_Vout *vout); void SDL_VoutFreeP(SDL_Vout **pvout); -int SDL_VoutDisplayYUVOverlay(SDL_Vout *vout, SDL_VoutOverlay *overlay); -int SDL_VoutSetOverlayFormat(SDL_Vout *vout, Uint32 overlay_format); +int SDL_VoutDisplayYUVOverlay(SDL_Vout *vout, SDL_VoutOverlay *overlay, SDL_TextureOverlay *sub_overlay); //convert a frame use vout. not free outFrame,when free vout the outFrame will free. if convert failed return greater then 0. int SDL_VoutConvertFrame(SDL_Vout *vout,int dst_format, const AVFrame *inFrame, const AVFrame **outFrame); - -#ifdef __APPLE__ -SDL_VoutOverlay *SDL_Vout_CreateOverlay_Apple(int width, int height, int src_format, int cvpixelbufferpool, SDL_Vout *vout); -#else SDL_VoutOverlay *SDL_Vout_CreateOverlay(int width, int height, int src_format, SDL_Vout *vout); -#endif int SDL_VoutLockYUVOverlay(SDL_VoutOverlay *overlay); int SDL_VoutUnlockYUVOverlay(SDL_VoutOverlay *overlay); diff --git a/ijkmedia/ijksdl/ios/IJKSDLGLView.h b/ijkmedia/ijksdl/ios/IJKSDLGLView.h deleted file mode 100644 index 08eff6f885..0000000000 --- a/ijkmedia/ijksdl/ios/IJKSDLGLView.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * IJKSDLGLView.h - * - * Copyright (c) 2013 Bilibili - * Copyright (c) 2013 Zhang Rui - * - * based on https://github.com/kolyvan/kxmovie - * - * This file is part of ijkPlayer. - * - * ijkPlayer is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * ijkPlayer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with ijkPlayer; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#import "IJKVideoRenderingProtocol.h" - -@interface IJKSDLGLView : UIView - -@end diff --git a/ijkmedia/ijksdl/ios/IJKSDLGLView.m b/ijkmedia/ijksdl/ios/IJKSDLGLView.m deleted file mode 100644 index 556ab76451..0000000000 --- a/ijkmedia/ijksdl/ios/IJKSDLGLView.m +++ /dev/null @@ -1,985 +0,0 @@ -/* - * IJKSDLGLView.m - * - * Copyright (c) 2013 Bilibili - * Copyright (c) 2013 Zhang Rui - * - * based on https://github.com/kolyvan/kxmovie - * - * This file is part of ijkPlayer. - * - * ijkPlayer is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * ijkPlayer is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with ijkPlayer; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#import "IJKSDLGLView.h" -#include "ijksdl/ijksdl_timer.h" -#include "ijksdl/apple/ijksdl_ios.h" -#include "ijksdl/ijksdl_gles2.h" -#import "IJKSDLTextureString.h" -#import "IJKMediaPlayback.h" -#import "../gles2/internal.h" - -#define kHDRAnimationMaxCount 90 - -typedef NS_ENUM(NSInteger, IJKSDLGLViewApplicationState) { - IJKSDLGLViewApplicationUnknownState = 0, - IJKSDLGLViewApplicationForegroundState = 1, - IJKSDLGLViewApplicationBackgroundState = 2 -}; - -@interface _IJKSDLSubTexture : NSObject - -@property(nonatomic) GLuint texture; -@property(nonatomic) int w; -@property(nonatomic) int h; - -@end - -@implementation _IJKSDLSubTexture - -- (void)dealloc -{ - if (_texture) { - glDeleteTextures(1, &_texture); - } -} - -- (GLuint)texture -{ - return _texture; -} - -- (instancetype)initWithCVPixelBuffer:(CVPixelBufferRef)pixelBuff -{ - self = [super init]; - if (self) { - - self.w = (int)CVPixelBufferGetWidth(pixelBuff); - self.h = (int)CVPixelBufferGetHeight(pixelBuff); - - // Create a texture object that you apply to the model. - glGenTextures(1, &_texture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, _texture); - - int texutres[3] = {_texture,0,0}; - ijk_upload_texture_with_cvpixelbuffer(pixelBuff, texutres); -// glTexImage2D 不能处理字节对齐问题!会找成字幕倾斜显示,实际上有多余的padding填充,读取有误产生错行导致的 -// // Set up filter and wrap modes for the texture object. -// glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -// glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -// glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -// glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -// -// GLsizei width = (GLsizei)CVPixelBufferGetWidth(pixelBuff); -// GLsizei height = (GLsizei)CVPixelBufferGetHeight(pixelBuff); -// CVPixelBufferLockBaseAddress(pixelBuff, kCVPixelBufferLock_ReadOnly); -// void *src = CVPixelBufferGetBaseAddress(pixelBuff); -// glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, src); -// CVPixelBufferUnlockBaseAddress(pixelBuff, kCVPixelBufferLock_ReadOnly); - glBindTexture(GL_TEXTURE_2D, 0); - } - return self; -} - -+ (instancetype)generate:(CVPixelBufferRef)pixel -{ - return [[self alloc] initWithCVPixelBuffer:pixel]; -} - -@end - -@interface IJKSDLGLView() - -@property(atomic) IJKOverlayAttach *currentAttach; -@property(atomic,strong) NSRecursiveLock *glActiveLock; -@property(atomic) BOOL glActivePaused; -@property(nonatomic) NSInteger videoDegrees; -@property(nonatomic) CGSize videoNaturalSize; -//display window size / screen -@property(atomic) float displayScreenScale; -//display window size / video size -@property(atomic) float displayVideoScale; -@property(atomic) GLint backingWidth; -@property(atomic) GLint backingHeight; -@property(atomic) BOOL subtitlePreferenceChanged; -@property(atomic,getter=isRenderBufferInvalidated) BOOL renderBufferInvalidated; -@property(assign) int hdrAnimationFrameCount; - -@end - -@implementation IJKSDLGLView { - EAGLContext *_context; - GLuint _framebuffer; - GLuint _renderbuffer; - GLint _backingWidth; - GLint _backingHeight; - - int _frameCount; - - int64_t _lastFrameTime; - - IJK_GLES2_Renderer *_renderer; - int _rendererGravity; - - - int _tryLockErrorCount; - BOOL _didSetupGL; - BOOL _didStopGL; - BOOL _didLockedDueToMovedToWindow; - BOOL _shouldLockWhileBeingMovedToWindow; - NSMutableArray *_registeredNotifications; - - IJKSDLGLViewApplicationState _applicationState; -} - -@synthesize scaleFactor = _scaleFactor; -@synthesize scalingMode = _scalingMode; -// subtitle preference -@synthesize subtitlePreference = _subtitlePreference; -// rotate preference -@synthesize rotatePreference = _rotatePreference; -// color conversion perference -@synthesize colorPreference = _colorPreference; -// user defined display aspect ratio -@synthesize darPreference = _darPreference; -@synthesize preventDisplay; -@synthesize showHdrAnimation = _showHdrAnimation; - -+ (Class) layerClass -{ - return [CAEAGLLayer class]; -} - -- (id) initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - _tryLockErrorCount = 0; - _shouldLockWhileBeingMovedToWindow = YES; - self.glActiveLock = [[NSRecursiveLock alloc] init]; - _registeredNotifications = [[NSMutableArray alloc] init]; - - _subtitlePreference = (IJKSDLSubtitlePreference){1.0, 0xFFFFFF, 0.1}; - _rotatePreference = (IJKSDLRotatePreference){IJKSDLRotateNone, 0.0}; - _colorPreference = (IJKSDLColorConversionPreference){1.0, 1.0, 1.0}; - _darPreference = (IJKSDLDARPreference){0.0}; - _displayScreenScale = 1.0; - _displayVideoScale = 1.0; - _rendererGravity = IJK_GLES2_GRAVITY_RESIZE_ASPECT; - - [self registerApplicationObservers]; - - _didSetupGL = NO; - if ([self isApplicationActive] == YES) - [self setupGLOnce]; - } - - return self; -} - -- (void)willMoveToWindow:(UIWindow *)newWindow -{ - if (!_shouldLockWhileBeingMovedToWindow) { - [super willMoveToWindow:newWindow]; - return; - } - if (newWindow && !_didLockedDueToMovedToWindow) { - [self lockGLActive]; - _didLockedDueToMovedToWindow = YES; - } - [super willMoveToWindow:newWindow]; -} - -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - if (self.window && _didLockedDueToMovedToWindow) { - [self unlockGLActive]; - _didLockedDueToMovedToWindow = NO; - } -} - -- (BOOL)setupEAGLContext:(EAGLContext *)context -{ - glGenFramebuffers(1, &_framebuffer); - glGenRenderbuffers(1, &_renderbuffer); - glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); - glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer); - [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderbuffer); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - NSLog(@"failed to make complete framebuffer object %x\n", status); - return NO; - } - - GLenum glError = glGetError(); - if (GL_NO_ERROR != glError) { - NSLog(@"failed to setup GL %x\n", glError); - return NO; - } - - return YES; -} - -- (CAEAGLLayer *)eaglLayer -{ - return (CAEAGLLayer*) self.layer; -} - -- (BOOL)setupGL -{ - if (_didSetupGL) - return YES; - - CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer; - eaglLayer.opaque = YES; - eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, - kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, - nil]; - - _scaleFactor = [[UIScreen mainScreen] scale]; - if (_scaleFactor < 0.1f) - _scaleFactor = 1.0f; - - [eaglLayer setContentsScale:_scaleFactor]; - - _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - if (_context == nil) { - NSLog(@"failed to setup EAGLContext\n"); - return NO; - } - - EAGLContext *prevContext = [EAGLContext currentContext]; - [EAGLContext setCurrentContext:_context]; - _didSetupGL = NO; - if ([self setupEAGLContext:_context]) { - NSLog(@"OK setup GL\n"); - _didSetupGL = YES; - } - - [EAGLContext setCurrentContext:prevContext]; - return _didSetupGL; -} - -- (BOOL)setupGLOnce -{ - if (_didSetupGL) - return YES; - - if (![self tryLockGLActive]) - return NO; - - BOOL didSetupGL = [self setupGL]; - [self unlockGLActive]; - return didSetupGL; -} - -- (void)setShowHdrAnimation:(BOOL)showHdrAnimation -{ - if (_showHdrAnimation != showHdrAnimation) { - _showHdrAnimation = showHdrAnimation; - self.hdrAnimationFrameCount = 0; - } -} - -- (void)videoZRotateDegrees:(NSInteger)degrees -{ - self.videoDegrees = degrees; -} - -- (void)videoNaturalSizeChanged:(CGSize)size -{ - self.videoNaturalSize = size; - CGRect viewBounds = [self bounds]; - if (!CGSizeEqualToSize(CGSizeZero, self.videoNaturalSize)) { - self.displayVideoScale = FFMIN(1.0 * viewBounds.size.width / self.videoNaturalSize.width, 1.0 * viewBounds.size.height / self.videoNaturalSize.height); - } -} - -- (BOOL)isApplicationActive -{ - switch (_applicationState) { - case IJKSDLGLViewApplicationForegroundState: - return YES; - case IJKSDLGLViewApplicationBackgroundState: - return NO; - default: { - UIApplicationState appState = [UIApplication sharedApplication].applicationState; - switch (appState) { - case UIApplicationStateActive: - return YES; - case UIApplicationStateInactive: - case UIApplicationStateBackground: - default: - return NO; - } - } - } -} - -- (void)dealloc -{ - [self lockGLActive]; - - _didStopGL = YES; - - EAGLContext *prevContext = [EAGLContext currentContext]; - [EAGLContext setCurrentContext:_context]; - - IJK_GLES2_Renderer_reset(_renderer); - IJK_GLES2_Renderer_freeP(&_renderer); - - if (_framebuffer) { - glDeleteFramebuffers(1, &_framebuffer); - _framebuffer = 0; - } - - if (_renderbuffer) { - glDeleteRenderbuffers(1, &_renderbuffer); - _renderbuffer = 0; - } - - glFinish(); - - [EAGLContext setCurrentContext:prevContext]; - - _context = nil; - - [self unregisterApplicationObservers]; - - [self unlockGLActive]; -} - -- (void)setScaleFactor:(CGFloat)scaleFactor -{ - _scaleFactor = scaleFactor; - [self resetViewPort]; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - if (self.window.screen != nil) { - _scaleFactor = self.window.screen.scale; - } - [self resetViewPort]; -} - -- (void)resetViewPort -{ - CGSize viewSize = [self bounds].size; - //TODO:need check airplay - CGSize viewSizePixels = CGSizeMake(viewSize.width * _scaleFactor, viewSize.height * _scaleFactor); - - if (self.backingWidth != viewSizePixels.width || self.backingHeight != viewSizePixels.height) { - self.backingWidth = viewSizePixels.width; - self.backingHeight = viewSizePixels.height; - - CGSize screenSize = [[UIScreen mainScreen]bounds].size;; - self.displayScreenScale = FFMIN(1.0 * viewSize.width / screenSize.width,1.0 * viewSize.height / screenSize.height); - if (!CGSizeEqualToSize(CGSizeZero, self.videoNaturalSize)) { - self.displayVideoScale = FFMIN(1.0 * viewSize.width / self.videoNaturalSize.width,1.0 * viewSize.height / self.videoNaturalSize.height); - } - - if (IJK_GLES2_Renderer_isValid(_renderer)) { - IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, self.backingWidth, self.backingHeight); - } - } - - self.renderBufferInvalidated = YES; -} - -- (BOOL)setupRendererIfNeed:(IJKOverlayAttach *)attach -{ - if (attach == nil) - return _renderer != nil; - - Uint32 cv_format = CVPixelBufferGetPixelFormatType(attach.videoPicture); - - if (!IJK_GLES2_Renderer_isValid(_renderer) || - !IJK_GLES2_Renderer_isFormat(_renderer, cv_format)) { - - IJK_GLES2_Renderer_reset(_renderer); - IJK_GLES2_Renderer_freeP(&_renderer); - - _renderer = IJK_GLES2_Renderer_createApple(attach.videoPicture, 0); - if (!IJK_GLES2_Renderer_isValid(_renderer)) - return NO; - - if (!IJK_GLES2_Renderer_use(_renderer)) - return NO; - - IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, self.backingWidth, self.backingHeight); - - IJK_GLES2_Renderer_updateRotate(_renderer, _rotatePreference.type, _rotatePreference.degrees); - - IJK_GLES2_Renderer_updateAutoZRotate(_renderer, attach.autoZRotate); - - IJK_GLES2_Renderer_updateSubtitleBottomMargin(_renderer, _subtitlePreference.bottomMargin); - - IJK_GLES2_Renderer_updateColorConversion(_renderer, _colorPreference.brightness, _colorPreference.saturation,_colorPreference.contrast); - - IJK_GLES2_Renderer_updateUserDefinedDAR(_renderer, _darPreference.ratio); - } - - return YES; -} - -- (void)doUploadSubtitle:(IJKOverlayAttach *)attach -{ - _IJKSDLSubTexture * subTexture = attach.subTexture; - if (subTexture) { - float ratio = 1.0; - if (attach.sub.pixels) { - ratio = self.subtitlePreference.ratio * self.displayVideoScale * 1.5; - } else { - //for text subtitle scale display_scale. - ratio *= self.displayScreenScale; - } - - IJK_GLES2_Renderer_beginDrawSubtitle(_renderer); - IJK_GLES2_Renderer_updateSubtitleVertex(_renderer, ratio * subTexture.w, ratio * subTexture.h); - if (IJK_GLES2_Renderer_uploadSubtitleTexture(_renderer, subTexture.texture, subTexture.w, subTexture.h)) { - IJK_GLES2_Renderer_drawArrays(); - } else { - ALOGE("[GL] GLES2 Render Subtitle failed\n"); - } - IJK_GLES2_Renderer_endDrawSubtitle(_renderer); - } -} - -- (void)doUploadVideoPicture:(IJKOverlayAttach *)attach -{ - if (attach.videoPicture) { - if (IJK_GLES2_Renderer_updateVertex2(_renderer, attach.h, attach.w, attach.pixelW, attach.sarNum, attach.sarDen)) { - float hdrPer = 1.0; - if (self.showHdrAnimation) { - if (self.hdrAnimationFrameCount == 0) { - [[NSNotificationCenter defaultCenter] postNotificationName:IJKMoviePlayerHDRAnimationStateChanged object:self userInfo:@{@"state":@(1)}]; - } else if (self.hdrAnimationFrameCount == kHDRAnimationMaxCount) { - [[NSNotificationCenter defaultCenter] postNotificationName:IJKMoviePlayerHDRAnimationStateChanged object:self userInfo:@{@"state":@(2)}]; - } - - if (self.hdrAnimationFrameCount <= kHDRAnimationMaxCount) { - self.hdrAnimationFrameCount++; - hdrPer = 1.0 * self.hdrAnimationFrameCount / kHDRAnimationMaxCount; - } - } - IJK_GLES2_Renderer_updateHdrAnimationProgress(_renderer, hdrPer); - if (IJK_GLES2_Renderer_uploadTexture(_renderer, (void *)attach.videoPicture)) { - IJK_GLES2_Renderer_drawArrays(); - } else { - ALOGE("[GL] Renderer_updateVertex failed\n"); - } - } else { - ALOGE("[GL] Renderer_updateVertex failed\n"); - } - } -} - -- (void)doRefreshCurrentAttach:(IJKOverlayAttach *)currentAttach -{ - if (!currentAttach) { - return; - } - - //update subtitle if need - if (self.subtitlePreferenceChanged) { - if (currentAttach.sub.text) { - CVPixelBufferRef subPicture = [self _generateSubtitlePixel:currentAttach.sub.text]; - currentAttach.subTexture = [_IJKSDLSubTexture generate:subPicture]; - } - self.subtitlePreferenceChanged = NO; - } - - [self doDisplayVideoPicAndSubtitle:currentAttach]; -} - -- (void)setNeedsRefreshCurrentPic -{ - [self doRefreshCurrentAttach:self.currentAttach]; -} - -- (CVPixelBufferRef)_generateSubtitlePixel:(NSString *)subtitle -{ - if (subtitle.length == 0) { - return NULL; - } - - IJKSDLSubtitlePreference sp = self.subtitlePreference; - - float ratio = sp.ratio; - int32_t bgrValue = sp.color; - //iPhone上以800为标准,定义出字幕字体默认大小为60pt - float scale = 1.0; - CGSize screenSize = [[UIScreen mainScreen]bounds].size; - - NSInteger degrees = self.videoDegrees; - if (degrees / 90 % 2 == 1) { - scale = screenSize.height / 800.0; - } else { - scale = screenSize.width / 800.0; - } - //字幕默认配置 - NSMutableDictionary * attributes = [[NSMutableDictionary alloc] init]; - - UIFont *subtitleFont = [UIFont systemFontOfSize:ratio * scale * 60]; - [attributes setObject:subtitleFont forKey:NSFontAttributeName]; - - UIColor *subtitleColor = [UIColor colorWithRed:((float)(bgrValue & 0xFF)) / 255.0 green:((float)((bgrValue & 0xFF00) >> 8)) / 255.0 blue:(float)(((bgrValue & 0xFF0000) >> 16)) / 255.0 alpha:1.0]; - - [attributes setObject:subtitleColor forKey:NSForegroundColorAttributeName]; - - IJKSDLTextureString *textureString = [[IJKSDLTextureString alloc] initWithString:subtitle withAttributes:attributes]; - - return [textureString createPixelBuffer]; -} - -- (CVPixelBufferRef)_generateSubtitlePixelFromPicture:(IJKSDLSubtitle*)pict -{ - CVPixelBufferRef pixelBuffer = NULL; - NSDictionary *options = @{ - (__bridge NSString*)kCVPixelBufferOpenGLCompatibilityKey : @YES, - (__bridge NSString*)kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary] - }; - - CVReturn ret = CVPixelBufferCreate(kCFAllocatorDefault, pict.w, pict.h, kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef)options, &pixelBuffer); - - NSParameterAssert(ret == kCVReturnSuccess && pixelBuffer != NULL); - - CVPixelBufferLockBaseAddress(pixelBuffer, 0); - - uint8_t *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer); - int linesize = (int)CVPixelBufferGetBytesPerRow(pixelBuffer); - - uint8_t *dst_data[4] = {baseAddress,NULL,NULL,NULL}; - int dst_linesizes[4] = {linesize,0,0,0}; - - const uint8_t *src_data[4] = {pict.pixels,NULL,NULL,NULL}; - const int src_linesizes[4] = {pict.w * 4,0,0,0}; - - av_image_copy(dst_data, dst_linesizes, src_data, src_linesizes, AV_PIX_FMT_BGRA, pict.w, pict.h); - - CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); - - if (kCVReturnSuccess == ret) { - return pixelBuffer; - } else { - return NULL; - } -} - -- (BOOL)doDisplayVideoPicAndSubtitle:(IJKOverlayAttach *)attach -{ - if (!attach) { - return NO; - } - - if (_didSetupGL == NO) - return NO; - - if ([self isApplicationActive] == NO) - return NO; - - if (![self tryLockGLActive]) { - if (0 == (_tryLockErrorCount % 100)) { - NSLog(@"IJKSDLGLView:display: unable to tryLock GL active: %d\n", _tryLockErrorCount); - } - _tryLockErrorCount++; - return NO; - } - - BOOL ok = YES; - _tryLockErrorCount = 0; - if (_context && !_didStopGL) { - EAGLContext *prevContext = [EAGLContext currentContext]; - [EAGLContext setCurrentContext:_context]; - { - [[self eaglLayer] setContentsScale:_scaleFactor]; - if ([self setupRendererIfNeed:attach] && IJK_GLES2_Renderer_isValid(_renderer)) { - glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer); - glViewport(0, 0, self.backingWidth, self.backingHeight); - glClear(GL_COLOR_BUFFER_BIT); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - if (self.isRenderBufferInvalidated) { - self.renderBufferInvalidated = NO; - [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer]; - } - - //for video - [self doUploadVideoPicture:attach]; - //for subtitle - [self doUploadSubtitle:attach]; - } else { - ALOGW("IJKSDLGLView: not ready.\n"); - ok = NO; - } - - [_context presentRenderbuffer:GL_RENDERBUFFER]; - } - [EAGLContext setCurrentContext:prevContext]; - } - - [self unlockGLActive]; - return ok; -} - -- (void)generateSubTexture:(IJKOverlayAttach *)attach -{ - IJKSDLSubtitle *sub = attach.sub; - CVPixelBufferRef subRef = NULL; - if (sub.text.length > 0) { - subRef = [self _generateSubtitlePixel:sub.text]; - } else if (sub.pixels != NULL) { - subRef = [self _generateSubtitlePixelFromPicture:sub]; - } - if (subRef) { - [self tryLockGLActive]; - EAGLContext *prevContext = [EAGLContext currentContext]; - [EAGLContext setCurrentContext:_context]; - attach.subTexture = [_IJKSDLSubTexture generate:subRef]; - [self unlockGLActive]; - [EAGLContext setCurrentContext:prevContext]; - CVPixelBufferRelease(subRef); - } -} - -- (BOOL)displayAttach:(IJKOverlayAttach *)attach -{ - if (!attach) { - ALOGW("IJKSDLGLView: overlay is nil\n"); - return NO; - } - - //generate current subtitle. - [self generateSubTexture:attach]; - - if (self.subtitlePreferenceChanged) { - self.subtitlePreferenceChanged = NO; - } - //hold the attach as current. - self.currentAttach = attach; - - if (self.preventDisplay) { - return YES; - } - - return [self doDisplayVideoPicAndSubtitle:self.currentAttach]; -} - -#pragma mark AppDelegate - -- (void) lockGLActive -{ - [self.glActiveLock lock]; -} - -- (void) unlockGLActive -{ - [self.glActiveLock unlock]; -} - -- (BOOL) tryLockGLActive -{ - if (![self.glActiveLock tryLock]) - return NO; - - /*- - if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive && - [UIApplication sharedApplication].applicationState != UIApplicationStateInactive) { - [self.appLock unlock]; - return NO; - } - */ - - if (self.glActivePaused) { - [self.glActiveLock unlock]; - return NO; - } - - return YES; -} - -- (void)toggleGLPaused:(BOOL)paused -{ - [self lockGLActive]; - if (!self.glActivePaused && paused) { - if (_context != nil) { - EAGLContext *prevContext = [EAGLContext currentContext]; - [EAGLContext setCurrentContext:_context]; - glFinish(); - [EAGLContext setCurrentContext:prevContext]; - } - } - self.glActivePaused = paused; - [self unlockGLActive]; -} - -- (void)registerApplicationObservers -{ - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillEnterForeground) - name:UIApplicationWillEnterForegroundNotification - object:nil]; - [_registeredNotifications addObject:UIApplicationWillEnterForegroundNotification]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive) - name:UIApplicationDidBecomeActiveNotification - object:nil]; - [_registeredNotifications addObject:UIApplicationDidBecomeActiveNotification]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillResignActive) - name:UIApplicationWillResignActiveNotification - object:nil]; - [_registeredNotifications addObject:UIApplicationWillResignActiveNotification]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidEnterBackground) - name:UIApplicationDidEnterBackgroundNotification - object:nil]; - [_registeredNotifications addObject:UIApplicationDidEnterBackgroundNotification]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillTerminate) - name:UIApplicationWillTerminateNotification - object:nil]; - [_registeredNotifications addObject:UIApplicationWillTerminateNotification]; -} - -- (void)unregisterApplicationObservers -{ - for (NSString *name in _registeredNotifications) { - [[NSNotificationCenter defaultCenter] removeObserver:self - name:name - object:nil]; - } -} - -- (void)applicationWillEnterForeground -{ - NSLog(@"IJKSDLGLView:applicationWillEnterForeground: %d", (int)[UIApplication sharedApplication].applicationState); - [self setupGLOnce]; - _applicationState = IJKSDLGLViewApplicationForegroundState; - [self toggleGLPaused:NO]; -} - -- (void)applicationDidBecomeActive -{ - NSLog(@"IJKSDLGLView:applicationDidBecomeActive: %d", (int)[UIApplication sharedApplication].applicationState); - [self setupGLOnce]; - [self toggleGLPaused:NO]; -} - -- (void)applicationWillResignActive -{ - NSLog(@"IJKSDLGLView:applicationWillResignActive: %d", (int)[UIApplication sharedApplication].applicationState); - [self toggleGLPaused:YES]; - glFinish(); -} - -- (void)applicationDidEnterBackground -{ - NSLog(@"IJKSDLGLView:applicationDidEnterBackground: %d", (int)[UIApplication sharedApplication].applicationState); - _applicationState = IJKSDLGLViewApplicationBackgroundState; - [self toggleGLPaused:YES]; - glFinish(); -} - -- (void)applicationWillTerminate -{ - NSLog(@"IJKSDLGLView:applicationWillTerminate: %d", (int)[UIApplication sharedApplication].applicationState); - [self toggleGLPaused:YES]; -} - -#pragma mark snapshot - -- (UIImage*)snapshot -{ - [self lockGLActive]; - - UIImage *image = [self snapshotInternal]; - - [self unlockGLActive]; - - return image; -} - -- (UIImage*)snapshotInternal -{ - if (isIOS7OrLater()) { - return [self snapshotInternalOnIOS7AndLater]; - } else { - return [self snapshotInternalOnIOS6AndBefore]; - } -} - -- (UIImage*)snapshotInternalOnIOS7AndLater -{ - if (CGSizeEqualToSize(self.bounds.size, CGSizeZero)) { - return nil; - } - UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0); - // Render our snapshot into the image context - [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO]; - - // Grab the image from the context - UIImage *complexViewImage = UIGraphicsGetImageFromCurrentImageContext(); - // Finish using the context - UIGraphicsEndImageContext(); - - return complexViewImage; -} - -- (UIImage*)snapshotInternalOnIOS6AndBefore -{ - EAGLContext *prevContext = [EAGLContext currentContext]; - [EAGLContext setCurrentContext:_context]; - - GLint backingWidth, backingHeight; - - // Bind the color renderbuffer used to render the OpenGL ES view - // If your application only creates a single color renderbuffer which is already bound at this point, - // this call is redundant, but it is needed if you're dealing with multiple renderbuffers. - // Note, replace "viewRenderbuffer" with the actual name of the renderbuffer object defined in your class. - glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer); - - // Get the size of the backing CAEAGLLayer - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight); - - NSInteger x = 0, y = 0, width = backingWidth, height = backingHeight; - NSInteger dataLength = width * height * 4; - GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte)); - - // Read pixel data from the framebuffer - glPixelStorei(GL_PACK_ALIGNMENT, 4); - glReadPixels((int)x, (int)y, (int)width, (int)height, GL_RGBA, GL_UNSIGNED_BYTE, data); - - // Create a CGImage with the pixel data - // If your OpenGL ES content is opaque, use kCGImageAlphaNoneSkipLast to ignore the alpha channel - // otherwise, use kCGImageAlphaPremultipliedLast - CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL); - CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); - CGImageRef iref = CGImageCreate(width, height, 8, 32, width * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast, - ref, NULL, true, kCGRenderingIntentDefault); - - [EAGLContext setCurrentContext:prevContext]; - - // OpenGL ES measures data in PIXELS - // Create a graphics context with the target size measured in POINTS - UIGraphicsBeginImageContext(CGSizeMake(width, height)); - - CGContextRef cgcontext = UIGraphicsGetCurrentContext(); - // UIKit coordinate system is upside down to GL/Quartz coordinate system - // Flip the CGImage by rendering it to the flipped bitmap context - // The size of the destination area is measured in POINTS - CGContextSetBlendMode(cgcontext, kCGBlendModeCopy); - CGContextDrawImage(cgcontext, CGRectMake(0.0, 0.0, width, height), iref); - - // Retrieve the UIImage from the current context - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - // Clean up - free(data); - CFRelease(ref); - CFRelease(colorspace); - CGImageRelease(iref); - - return image; -} - -#pragma mark - override setter methods - -- (void)setScalingMode:(IJKMPMovieScalingMode)scalingMode -{ - switch (scalingMode) { - case IJKMPMovieScalingModeFill: - _rendererGravity = IJK_GLES2_GRAVITY_RESIZE; - break; - case IJKMPMovieScalingModeAspectFit: - _rendererGravity = IJK_GLES2_GRAVITY_RESIZE_ASPECT; - break; - case IJKMPMovieScalingModeAspectFill: - _rendererGravity = IJK_GLES2_GRAVITY_RESIZE_ASPECT_FILL; - break; - } - _scalingMode = scalingMode; - if (IJK_GLES2_Renderer_isValid(_renderer)) { - IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, self.backingWidth, self.backingHeight); - } -} - -- (void)setRotatePreference:(IJKSDLRotatePreference)rotatePreference -{ - if (_rotatePreference.type != rotatePreference.type || _rotatePreference.degrees != rotatePreference.degrees) { - _rotatePreference = rotatePreference; - if (IJK_GLES2_Renderer_isValid(_renderer)) { - IJK_GLES2_Renderer_updateRotate(_renderer, _rotatePreference.type, _rotatePreference.degrees); - } - } -} - -- (void)setColorPreference:(IJKSDLColorConversionPreference)colorPreference -{ - if (_colorPreference.brightness != colorPreference.brightness || _colorPreference.saturation != colorPreference.saturation || _colorPreference.contrast != colorPreference.contrast) { - _colorPreference = colorPreference; - if (IJK_GLES2_Renderer_isValid(_renderer)) { - IJK_GLES2_Renderer_updateColorConversion(_renderer, _colorPreference.brightness, _colorPreference.saturation,_colorPreference.contrast); - } - } -} - -- (void)setDarPreference:(IJKSDLDARPreference)darPreference -{ - if (_darPreference.ratio != darPreference.ratio) { - _darPreference = darPreference; - if (IJK_GLES2_Renderer_isValid(_renderer)) { - IJK_GLES2_Renderer_updateUserDefinedDAR(_renderer, _darPreference.ratio); - } - } -} - -- (void)setSubtitlePreference:(IJKSDLSubtitlePreference)subtitlePreference -{ - if (_subtitlePreference.bottomMargin != subtitlePreference.bottomMargin) { - _subtitlePreference = subtitlePreference; - if (IJK_GLES2_Renderer_isValid(_renderer)) { - IJK_GLES2_Renderer_updateSubtitleBottomMargin(_renderer, _subtitlePreference.bottomMargin); - } - } - - if (_subtitlePreference.ratio != subtitlePreference.ratio || _subtitlePreference.color != subtitlePreference.color) { - _subtitlePreference = subtitlePreference; - self.subtitlePreferenceChanged = YES; - } -} - -- (NSString *)name -{ - return @"OpenGL-ES"; -} - -@end diff --git a/ijkmedia/ijksdl/mac/IJKSDLGLView.m b/ijkmedia/ijksdl/mac/IJKSDLGLView.m index 6e58deef32..df2a2f7ac1 100644 --- a/ijkmedia/ijksdl/mac/IJKSDLGLView.m +++ b/ijkmedia/ijksdl/mac/IJKSDLGLView.m @@ -45,6 +45,14 @@ on low macOS (below 10.13) CGLLock is no effect for multiple NSOpenGLContext! macos 10.14 later use global single thread not smooth when create multil glview. */ +/* + 2024.4.25 + when create multiple glview,flushBuffer will present contenxt to screen in multiple thread, but + on low macOS (below 10.13) CGLLock is no effect for multiple NSOpenGLContext even though shared one context! + so on low macOS we use global NSLock. + every glview hold a render thread. + */ + #import "IJKSDLGLView.h" #import #import @@ -53,25 +61,14 @@ on low macOS (below 10.13) CGLLock is no effect for multiple NSOpenGLContext! #import "ijksdl_timer.h" #import "ijksdl_gles2.h" #import "ijksdl_vout_overlay_ffmpeg_hw.h" -#import "IJKSDLTextureString.h" #import "IJKMediaPlayback.h" #import "IJKSDLThread.h" #import "../gles2/internal.h" +#import "ijksdl_vout_ios_gles2.h" +#import "ijksdl_gpu_opengl_fbo_macos.h" #define kHDRAnimationMaxCount 90 -static IJKSDLThread *__ijk_global_thread; - -static IJKSDLThread * _globalThread_(void) -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - __ijk_global_thread = [[IJKSDLThread alloc] initWithName:@"ijk_global_render"]; - [__ijk_global_thread start]; - }); - return __ijk_global_thread; -} - // greather than 10.14 no need dispatch to global. static bool _is_low_os_version(void) { @@ -84,177 +81,44 @@ static bool _is_low_os_version(void) return true; } -static bool _is_need_dispath_to_global(void) +static NSLock *_gl_lock; + +static void lock_gl(NSOpenGLContext *ctx) { bool low_os = _is_low_os_version(); if (low_os) { - return true; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _gl_lock = [[NSLock alloc] init]; + }); + [_gl_lock lock]; } else { - return false; - } -} - -//for snapshot. - -@interface _IJKSDLFBO : NSObject - -@property(nonatomic) CGSize textureSize; -@property(nonatomic) GLuint fbo; -@property(nonatomic) GLuint colorTexture; - -@end - -@implementation _IJKSDLFBO - -- (void)dealloc -{ - if (_fbo) { - glDeleteFramebuffers(1, &_fbo); - } - - if (_colorTexture) { - glDeleteTextures(1, &_colorTexture); + CGLLockContext([ctx CGLContextObj]); } - - _textureSize = CGSizeZero; } -// Create texture and framebuffer objects to render and snapshot. -- (BOOL)canReuse:(CGSize)size +static void unlock_gl(NSOpenGLContext *ctx) { - if (CGSizeEqualToSize(CGSizeZero, size)) { - return NO; - } - - if (CGSizeEqualToSize(_textureSize, size) && _fbo && _colorTexture) { - return YES; + bool low_os = _is_low_os_version(); + if (low_os) { + [_gl_lock unlock]; } else { - return NO; - } -} - -- (instancetype)initWithSize:(CGSize)size -{ - self = [super init]; - if (self) { - // Create a texture object that you apply to the model. - glGenTextures(1, &_colorTexture); - glBindTexture(GL_TEXTURE_2D, _colorTexture); - - // Set up filter and wrap modes for the texture object. - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - // Allocate a texture image to which you can render to. Pass `NULL` for the data parameter - // becuase you don't need to load image data. You generate the image by rendering to the texture. - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - - glGenFramebuffers(1, &_fbo); - glBindFramebuffer(GL_FRAMEBUFFER, _fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _colorTexture, 0); - - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { - _textureSize = size; - return self; - } else { - #if DEBUG - NSAssert(NO, @"Failed to make complete framebuffer object %x.", glCheckFramebufferStatus(GL_FRAMEBUFFER)); - #endif - _textureSize = CGSizeZero; - return nil; - } - } - return nil; -} - -- (void)bind -{ - // Bind the snapshot FBO and render the scene. - glBindFramebuffer(GL_FRAMEBUFFER, _fbo); -} - -@end - -@interface _IJKSDLSubTexture : NSObject - -@property(nonatomic) GLuint texture; -@property(nonatomic) int w; -@property(nonatomic) int h; - -@end - -@implementation _IJKSDLSubTexture - -- (void)dealloc -{ - if (_texture) { - glDeleteTextures(1, &_texture); + CGLUnlockContext([ctx CGLContextObj]); } } -- (GLuint)texture -{ - return _texture; -} - -- (instancetype)initWithCVPixelBuffer:(CVPixelBufferRef)pixelBuff -{ - self = [super init]; - if (self) { - - self.w = (int)CVPixelBufferGetWidth(pixelBuff); - self.h = (int)CVPixelBufferGetHeight(pixelBuff); - - // Create a texture object that you apply to the model. - glGenTextures(1, &_texture); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_RECTANGLE, _texture); - - int texutres[3] = {_texture,0,0}; - ijk_upload_texture_with_cvpixelbuffer(pixelBuff, texutres); -// glTexImage2D 不能处理字节对齐问题!会找成字幕倾斜显示,实际上有多余的padding填充,读取有误产生错行导致的 -// // Set up filter and wrap modes for the texture object. -// glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -// glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -// glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -// glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -// -// GLsizei width = (GLsizei)CVPixelBufferGetWidth(pixelBuff); -// GLsizei height = (GLsizei)CVPixelBufferGetHeight(pixelBuff); -// CVPixelBufferLockBaseAddress(pixelBuff, kCVPixelBufferLock_ReadOnly); -// void *src = CVPixelBufferGetBaseAddress(pixelBuff); -// glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, src); -// CVPixelBufferUnlockBaseAddress(pixelBuff, kCVPixelBufferLock_ReadOnly); - glBindTexture(GL_TEXTURE_RECTANGLE, 0); - } - return self; -} - -+ (instancetype)generate:(CVPixelBufferRef)pixel -{ - return [[self alloc] initWithCVPixelBuffer:pixel]; -} - -@end - @interface IJKSDLGLView() @property(atomic) IJKOverlayAttach *currentAttach; - -@property(nonatomic) NSInteger videoDegrees; -@property(nonatomic) CGSize videoNaturalSize; -//display window size / screen -@property(atomic) float displayScreenScale; -//display window size / video size -@property(atomic) float displayVideoScale; +//view size +@property(assign) CGSize viewSize; @property(atomic) GLint backingWidth; @property(atomic) GLint backingHeight; -@property(atomic) BOOL subtitlePreferenceChanged; -@property(atomic) _IJKSDLFBO * fbo; +@property(atomic) IJKSDLOpenGLFBO * fbo; @property(atomic) IJKSDLThread *renderThread; @property(assign) int hdrAnimationFrameCount; +@property(nonatomic) NSOpenGLContext *sharedContext; +@property(nonatomic) NSArray *bgColor; @end @@ -265,52 +129,47 @@ @implementation IJKSDLGLView } @synthesize scalingMode = _scalingMode; -// subtitle preference -@synthesize subtitlePreference = _subtitlePreference; // rotate preference @synthesize rotatePreference = _rotatePreference; -// color conversion perference +// color conversion preference @synthesize colorPreference = _colorPreference; // user defined display aspect ratio @synthesize darPreference = _darPreference; @synthesize preventDisplay; @synthesize showHdrAnimation = _showHdrAnimation; +- (void)destroyRender +{ + self.fbo = nil; + IJK_GLES2_Renderer_freeP(&_renderer); +} + - (void)dealloc { - [self.renderThread performSelector:@selector(setFbo:) - withTarget:self - withObject:nil - waitUntilDone:YES]; - if (_renderer) { - IJK_GLES2_Renderer_freeP(&_renderer); + if (self.renderThread && [NSThread currentThread] != self.renderThread.thread) { + [self.renderThread performSelector:@selector(destroyRender) withTarget:self withObject:nil waitUntilDone:YES]; + } else { + [self destroyRender]; + } } [[NSNotificationCenter defaultCenter] removeObserver:self]; - if (self.renderThread != __ijk_global_thread) { - [self.renderThread stop]; - } + [self.renderThread stop]; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { - if (_is_need_dispath_to_global()) { - self.renderThread = _globalThread_(); - } else { - self.renderThread = [[IJKSDLThread alloc] initWithName:@"ijk_renderer"]; - [self.renderThread start]; - } + self.renderThread = [[IJKSDLThread alloc] initWithName:@"ijk_renderer"]; + [self.renderThread start]; [self setup]; - _subtitlePreference = (IJKSDLSubtitlePreference){1.0, 0xFFFFFF, 0.1}; + _rotatePreference = (IJKSDLRotatePreference){IJKSDLRotateNone, 0.0}; _colorPreference = (IJKSDLColorConversionPreference){1.0, 1.0, 1.0}; _darPreference = (IJKSDLDARPreference){0.0}; - _displayScreenScale = 1.0; - _displayVideoScale = 1.0; _rendererGravity = IJK_GLES2_GRAVITY_RESIZE_ASPECT; } return self; @@ -339,7 +198,7 @@ - (void)setup return; } - NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; + NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:[self sharedContext]]; #if ESSENTIAL_GL_PRACTICES_SUPPORT_GL3 && defined(DEBUG) // When we're using a CoreProfile context, crash if we call a legacy OpenGL function @@ -353,15 +212,32 @@ - (void)setup [self setPixelFormat:pf]; [self setOpenGLContext:context]; [self setWantsBestResolutionOpenGLSurface:YES]; -// [self setWantsExtendedDynamicRangeOpenGLSurface:YES]; - - ///Fix the default red background color on the Intel platform + // Synchronize buffer swaps with vertical refresh rate + GLint swapInt = 1; + [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; + //Fix the default red background color on the Intel platform + self.bgColor = @[@(0.0),@(0.0),@(0.0),@(1.0)]; +} + +- (void)applyClearColor +{ + if ([self.bgColor count] != 4) { + return; + } + if (self.backingWidth == 0 || self.backingHeight == 0) { + return; + } + NSArray *rgb = self.bgColor; + lock_gl([self openGLContext]); [[self openGLContext] makeCurrentContext]; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(0, 0, self.backingWidth, self.backingHeight); + glClearColor([rgb[0] floatValue], [rgb[2] floatValue], [rgb[2] floatValue], [rgb[3] floatValue]); glClear(GL_COLOR_BUFFER_BIT); [[self openGLContext]flushBuffer]; + unlock_gl([self openGLContext]); } - - (void)setShowHdrAnimation:(BOOL)showHdrAnimation { if (_showHdrAnimation != showHdrAnimation) { @@ -370,29 +246,6 @@ - (void)setShowHdrAnimation:(BOOL)showHdrAnimation } } -- (void)videoZRotateDegrees:(NSInteger)degrees -{ - self.videoDegrees = degrees; -} - -- (void)videoNaturalSizeChanged:(CGSize)size -{ - self.videoNaturalSize = size; - CGRect viewBounds = [self bounds]; - if (!CGSizeEqualToSize(CGSizeZero, self.videoNaturalSize)) { - self.displayVideoScale = FFMIN(1.0 * viewBounds.size.width / self.videoNaturalSize.width, 1.0 * viewBounds.size.height / self.videoNaturalSize.height); - } -} - -- (void)cleanSubtitle -{ - if (self.currentAttach.sub) { - self.currentAttach.sub = nil; - self.currentAttach.subTexture = nil; - [self setNeedsRefreshCurrentPic]; - } -} - - (BOOL)setupRendererIfNeed:(IJKOverlayAttach *)attach { if (attach == nil) @@ -414,7 +267,7 @@ - (BOOL)setupRendererIfNeed:(IJKOverlayAttach *)attach if (!IJK_GLES2_Renderer_isValid(_renderer)) return NO; - if (!IJK_GLES2_Renderer_use(_renderer)) + if (!IJK_GLES2_Renderer_init(_renderer)) return NO; IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, self.backingWidth, self.backingHeight); @@ -423,11 +276,12 @@ - (BOOL)setupRendererIfNeed:(IJKOverlayAttach *)attach IJK_GLES2_Renderer_updateAutoZRotate(_renderer, attach.autoZRotate); - IJK_GLES2_Renderer_updateSubtitleBottomMargin(_renderer, _subtitlePreference.bottomMargin); - IJK_GLES2_Renderer_updateColorConversion(_renderer, _colorPreference.brightness, _colorPreference.saturation,_colorPreference.contrast); IJK_GLES2_Renderer_updateUserDefinedDAR(_renderer, _darPreference.ratio); + } else { + if (!IJK_GLES2_Renderer_useProgram(_renderer)) + return NO; } return YES; } @@ -443,122 +297,37 @@ - (void)reshape [self resetViewPort]; } +- (void)viewDidChangeBackingProperties +{ + [super viewDidChangeBackingProperties]; + //here need a delay,wait intenal right, otherwise display wrong picture size. + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self resetViewPort]; + }); +} + - (void)resetViewPort { CGSize viewSize = [self bounds].size; CGSize viewSizePixels = [self convertSizeToBacking:viewSize]; - + if (CGSizeEqualToSize(CGSizeZero, viewSize)) { + return; + } if (self.backingWidth != viewSizePixels.width || self.backingHeight != viewSizePixels.height) { self.backingWidth = viewSizePixels.width; self.backingHeight = viewSizePixels.height; - - CGSize screenSize = [[[NSScreen screens] firstObject]frame].size; - self.displayScreenScale = FFMIN(1.0 * viewSize.width / screenSize.width,1.0 * viewSize.height / screenSize.height); - if (!CGSizeEqualToSize(CGSizeZero, self.videoNaturalSize)) { - self.displayVideoScale = FFMIN(1.0 * viewSize.width / self.videoNaturalSize.width,1.0 * viewSize.height / self.videoNaturalSize.height); - } - - if (IJK_GLES2_Renderer_isValid(_renderer)) { - IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, self.backingWidth, self.backingHeight); - } - + self.viewSize = viewSize; [self setNeedsRefreshCurrentPic]; } } -- (void)viewDidChangeBackingProperties -{ - [super viewDidChangeBackingProperties]; - [self resetViewPort]; -} - -- (CVPixelBufferRef)_generateSubtitlePixel:(NSString *)subtitle +- (void)doUploadSubtitle:(IJKOverlayAttach *)attach viewport:(CGSize)viewport { - if (subtitle.length == 0) { - return NULL; - } - - IJKSDLSubtitlePreference sp = self.subtitlePreference; - - float ratio = sp.ratio; - int32_t bgrValue = sp.color; - //以800为标准,定义出字幕字体默认大小为30pt - float scale = 1.0; - CGSize screenSize = [[[NSScreen screens] firstObject]frame].size; - - NSInteger degrees = self.videoDegrees; - if (degrees / 90 % 2 == 1) { - scale = screenSize.height / 800.0; - } else { - scale = screenSize.width / 800.0; - } - //字幕默认配置 - NSMutableDictionary * attributes = [[NSMutableDictionary alloc] init]; - - UIFont *subtitleFont = [UIFont systemFontOfSize:ratio * scale * 60]; - [attributes setObject:subtitleFont forKey:NSFontAttributeName]; - - NSColor *subtitleColor = [NSColor colorWithRed:((float)(bgrValue & 0xFF)) / 255.0 green:((float)((bgrValue & 0xFF00) >> 8)) / 255.0 blue:(float)(((bgrValue & 0xFF0000) >> 16)) / 255.0 alpha:1.0]; - - [attributes setObject:subtitleColor forKey:NSForegroundColorAttributeName]; - - IJKSDLTextureString *textureString = [[IJKSDLTextureString alloc] initWithString:subtitle withAttributes:attributes]; - - return [textureString createPixelBuffer]; -} - -- (CVPixelBufferRef)_generateSubtitlePixelFromPicture:(IJKSDLSubtitle*)pict -{ - CVPixelBufferRef pixelBuffer = NULL; - NSDictionary *options = @{ - (__bridge NSString*)kCVPixelBufferOpenGLCompatibilityKey : @YES, - (__bridge NSString*)kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary] - }; - - CVReturn ret = CVPixelBufferCreate(kCFAllocatorDefault, pict.w, pict.h, kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef)options, &pixelBuffer); - - if (ret != kCVReturnSuccess || pixelBuffer == NULL) { - ALOGE("CVPixelBufferCreate subtitle failed:%d",ret); - return NULL; - } - - CVPixelBufferLockBaseAddress(pixelBuffer, 0); - - uint8_t *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer); - int linesize = (int)CVPixelBufferGetBytesPerRow(pixelBuffer); - - uint8_t *dst_data[4] = {baseAddress,NULL,NULL,NULL}; - int dst_linesizes[4] = {linesize,0,0,0}; - - const uint8_t *src_data[4] = {pict.pixels,NULL,NULL,NULL}; - const int src_linesizes[4] = {pict.w * 4,0,0,0}; - - av_image_copy(dst_data, dst_linesizes, src_data, src_linesizes, AV_PIX_FMT_BGRA, pict.w, pict.h); - - CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); - - if (kCVReturnSuccess == ret) { - return pixelBuffer; - } else { - return NULL; - } -} - -- (void)doUploadSubtitle:(IJKOverlayAttach *)attach -{ - _IJKSDLSubTexture * subTexture = attach.subTexture; + idsubTexture = attach.subTexture; if (subTexture) { - float ratio = 1.0; - if (attach.sub.pixels) { - ratio = self.subtitlePreference.ratio * self.displayVideoScale * 1.5; - } - ratio *= self.displayScreenScale; - IJK_GLES2_Renderer_beginDrawSubtitle(_renderer); - - IJK_GLES2_Renderer_updateSubtitleVertex(_renderer, ratio * subTexture.w, ratio * subTexture.h); - - if (IJK_GLES2_Renderer_uploadSubtitleTexture(_renderer, subTexture.texture,subTexture.w,subTexture.h)) { + IJK_GLES2_Renderer_updateSubtitleVertex(_renderer, subTexture.w, subTexture.h); + if (IJK_GLES2_Renderer_uploadSubtitleTexture(_renderer, subTexture.texture, subTexture.w, subTexture.h)) { IJK_GLES2_Renderer_drawArrays(); } else { ALOGE("[GL] GLES2 Render Subtitle failed\n"); @@ -599,19 +368,9 @@ - (void)doUploadVideoPicture:(IJKOverlayAttach *)attach - (void)doRefreshCurrentAttach:(IJKOverlayAttach *)currentAttach { if (!currentAttach) { + [self applyClearColor]; return; } - - //update subtitle if need - if (self.subtitlePreferenceChanged) { - if (currentAttach.sub.text) { - CVPixelBufferRef subPicture = [self _generateSubtitlePixel:currentAttach.sub.text]; - currentAttach.subTexture = [_IJKSDLSubTexture generate:subPicture]; - CVPixelBufferRelease(subPicture); - } - self.subtitlePreferenceChanged = NO; - } - [self doDisplayVideoPicAndSubtitle:currentAttach]; } @@ -621,7 +380,7 @@ - (void)doDisplayVideoPicAndSubtitle:(IJKOverlayAttach *)attach return; } - CGLLockContext([[self openGLContext] CGLContextObj]); + lock_gl([self openGLContext]); [[self openGLContext] makeCurrentContext]; if ([self setupRendererIfNeed:attach] && IJK_GLES2_Renderer_isValid(_renderer)) { @@ -630,72 +389,49 @@ - (void)doDisplayVideoPicAndSubtitle:(IJKOverlayAttach *)attach glViewport(0, 0, self.backingWidth, self.backingHeight); glClear(GL_COLOR_BUFFER_BIT); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - + IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, self.backingWidth, self.backingHeight); //for video [self doUploadVideoPicture:attach]; //for subtitle - [self doUploadSubtitle:attach]; + [self doUploadSubtitle:attach viewport:CGSizeMake(self.backingWidth, self.backingHeight)]; } else { ALOGW("IJKSDLGLView: Renderer not ok.\n"); } - [[self openGLContext]flushBuffer]; - CGLUnlockContext([[self openGLContext] CGLContextObj]); + if (self.inLiveResize) { + glFlush(); + } else { + [[self openGLContext]flushBuffer]; + } + unlock_gl([self openGLContext]); } - (void)setNeedsRefreshCurrentPic { - //use single global thread! [self.renderThread performSelector:@selector(doRefreshCurrentAttach:) withTarget:self withObject:self.currentAttach waitUntilDone:NO]; } -- (void)generateSubTexture:(IJKOverlayAttach *)attach -{ - IJKSDLSubtitle *sub = attach.sub; - CVPixelBufferRef subRef = NULL; - if (sub.text.length > 0) { - subRef = [self _generateSubtitlePixel:sub.text]; - } else if (sub.pixels != NULL) { - subRef = [self _generateSubtitlePixelFromPicture:sub]; - } - if (subRef) { - CGLLockContext([[self openGLContext] CGLContextObj]); - [[self openGLContext] makeCurrentContext]; - attach.subTexture = [_IJKSDLSubTexture generate:subRef]; - CGLUnlockContext([[self openGLContext] CGLContextObj]); - CVPixelBufferRelease(subRef); - } -} - - (BOOL)displayAttach:(IJKOverlayAttach *)attach { - if (!attach) { - ALOGW("IJKSDLGLView: overlay is nil\n"); - return NO; - } - //overlay is not thread safe, maybe need dispatch from sub thread to main thread,so hold overlay's property to GLView. - - //generate current subtitle. - if (self.subtitlePreferenceChanged || self.currentAttach.sub != attach.sub) { - [self generateSubTexture:attach]; - } else if (self.currentAttach.sub) { - //reuse the expensive texture. - attach.subTexture = self.currentAttach.subTexture; - } + //in vout thread hold the attach,let currentAttach dealloc in vout thread,because it's texture overlay was created in vout thread,must keep same thread in macOS 10.12 is so important! + self.currentAttach = attach; - if (self.subtitlePreferenceChanged) { - self.subtitlePreferenceChanged = NO; + if (!attach.videoPicture) { + ALOGW("IJKSDLGLView: videiPicture is nil\n"); + return NO; } - //hold the attach as current. - self.currentAttach = attach; if (self.preventDisplay) { return YES; } + if (self.backingWidth == 0 || self.backingHeight == 0) { + return NO; + } + [self.renderThread performSelector:@selector(doDisplayVideoPicAndSubtitle:) withTarget:self withObject:attach @@ -703,39 +439,6 @@ - (BOOL)displayAttach:(IJKOverlayAttach *)attach return YES; } -- (void)initGL -{ - // The reshape function may have changed the thread to which our OpenGL - // context is attached before prepareOpenGL and initGL are called. So call - // makeCurrentContext to ensure that our OpenGL context current to this - // thread (i.e. makeCurrentContext directs all OpenGL calls on this thread - // to [self openGLContext]) - [[self openGLContext] makeCurrentContext]; - - // Synchronize buffer swaps with vertical refresh rate - GLint swapInt = 1; - [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; - - glClearColor(0.0, 0.0, 0.0, 1.0f); -} - -- (void)prepareOpenGL -{ - [super prepareOpenGL]; - - // Make all the OpenGL calls to setup rendering - // and build the necessary rendering objects - [self initGL]; -} - -- (void)windowWillClose:(NSNotification*)notification -{ - // Stop the display link when the window is closing because default - // OpenGL render buffers will be destroyed. If display link continues to - // fire without renderbuffers, OpenGL draw calls will set errors. - // todo -} - #pragma mark - for snapshot - (void)_snapshotEffectOriginWithSubtitle:(NSDictionary *)params @@ -751,7 +454,7 @@ - (void)_snapshotEffectOriginWithSubtitle:(NSDictionary *)params return; } - CGLLockContext([[self openGLContext] CGLContextObj]); + lock_gl([self openGLContext]); [[self openGLContext] makeCurrentContext]; //[self setupRendererIfNeed:attach]; CGImageRef img = NULL; @@ -763,12 +466,7 @@ - (void)_snapshotEffectOriginWithSubtitle:(NSDictionary *)params CGSize picSize = CGSizeMake(CVPixelBufferGetWidth(attach.videoPicture) * videoSar, CVPixelBufferGetHeight(attach.videoPicture)); //视频带有旋转 90 度倍数时需要将显示宽高交换后计算 if (IJK_GLES2_Renderer_isZRotate90oddMultiple(_renderer)) { - float pic_width = picSize.width; - float pic_height = picSize.height; - float tmp = pic_width; - pic_width = pic_height; - pic_height = tmp; - picSize = CGSizeMake(pic_width, pic_height); + picSize = CGSizeMake(picSize.height, picSize.width); } //保持用户定义宽高比 @@ -785,7 +483,7 @@ - (void)_snapshotEffectOriginWithSubtitle:(NSDictionary *)params } if (![self.fbo canReuse:picSize]) { - self.fbo = [[_IJKSDLFBO alloc] initWithSize:picSize]; + self.fbo = [[IJKSDLOpenGLFBO alloc] initWithSize:picSize]; } if (self.fbo) { @@ -793,7 +491,7 @@ - (void)_snapshotEffectOriginWithSubtitle:(NSDictionary *)params [self.fbo bind]; glViewport(0, 0, picSize.width, picSize.height); glClear(GL_COLOR_BUFFER_BIT); - + IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, picSize.width, picSize.height); if (!IJK_GLES2_Renderer_resetVao(_renderer)) ALOGE("[GL] Renderer_resetVao failed\n"); @@ -804,7 +502,7 @@ - (void)_snapshotEffectOriginWithSubtitle:(NSDictionary *)params } if (containSub) { - [self doUploadSubtitle:attach]; + [self doUploadSubtitle:attach viewport:picSize]; } img = [self _snapshotTheContextWithSize:picSize]; } else { @@ -812,8 +510,7 @@ - (void)_snapshotEffectOriginWithSubtitle:(NSDictionary *)params } [[self openGLContext]flushBuffer]; } - CGLUnlockContext([[self openGLContext] CGLContextObj]); - + unlock_gl([self openGLContext]); if (outImg && img) { *outImg = CGImageRetain(img); } @@ -893,7 +590,7 @@ - (CGImageRef)_snapshotTheContextWithSize:(const CGSize)size GLint bytesPerRow = width * 4; const GLint bitsPerPixel = 32; - CGContextRef ctx = _CreateCGBitmapContext(width, height, 8, 32, bytesPerRow, kCGBitmapByteOrderDefault |kCGImageAlphaNoneSkipLast); + CGContextRef ctx = _CreateCGBitmapContext(width, height, 8, 32, bytesPerRow, kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast); if (ctx) { void * bitmapData = CGBitmapContextGetData(ctx); if (bitmapData) { @@ -933,6 +630,7 @@ - (void)_snapshot_screen:(NSValue *)ptrValue CGLLockContext([openGLContext CGLContextObj]); [openGLContext makeCurrentContext]; + glBindFramebuffer(GL_FRAMEBUFFER, 0); CGImageRef img = [self _snapshotTheContextWithSize:size]; CGLUnlockContext([openGLContext CGLContextObj]); @@ -1045,25 +743,44 @@ - (void)setDarPreference:(IJKSDLDARPreference)darPreference } } -- (void)setSubtitlePreference:(IJKSDLSubtitlePreference)subtitlePreference +- (void)setBackgroundColor:(uint8_t)r g:(uint8_t)g b:(uint8_t)b { - if (_subtitlePreference.bottomMargin != subtitlePreference.bottomMargin) { - _subtitlePreference = subtitlePreference; - if (IJK_GLES2_Renderer_isValid(_renderer)) { - IJK_GLES2_Renderer_updateSubtitleBottomMargin(_renderer, _subtitlePreference.bottomMargin); - } + self.bgColor = @[@(r/255.0),@(g/255.0),@(b/255.0),@(1.0)]; + if (!self.currentAttach) { + [self doRefreshCurrentAttach:self.currentAttach]; } - - if (_subtitlePreference.ratio != subtitlePreference.ratio || _subtitlePreference.color != subtitlePreference.color) { - _subtitlePreference = subtitlePreference; - self.subtitlePreferenceChanged = YES; +} + +- (NSOpenGLContext *)sharedContext +{ + if (!_sharedContext) { + NSOpenGLPixelFormatAttribute attrs[] = + { + NSOpenGLPFAAccelerated, + NSOpenGLPFANoRecovery, + NSOpenGLPFADoubleBuffer, + NSOpenGLPFADepthSize, 24, + #if ! USE_LEGACY_OPENGL + NSOpenGLPFAOpenGLProfile,NSOpenGLProfileVersion3_2Core, + #endif + // NSOpenGLPFAAllowOfflineRenderers, 1, + 0 + }; + + NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + + if (pf) { + _sharedContext = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; + } else { + ALOGE("No OpenGL pixel format"); + } } + return _sharedContext; } -- (void)setBackgroundColor:(uint8_t)r g:(uint8_t)g b:(uint8_t)b +- (id)context { - [[self openGLContext] makeCurrentContext]; - glClearColor(r/255.0, g/255.0, b/255.0, 1.0f); + return [self sharedContext]; } - (NSString *)name @@ -1073,6 +790,13 @@ - (NSString *)name - (NSView *)hitTest:(NSPoint)point { + for (NSView *sub in [self subviews]) { + NSPoint pointInSelf = [self convertPoint:point fromView:self.superview]; + NSPoint pointInSub = [self convertPoint:pointInSelf toView:sub]; + if (NSPointInRect(pointInSub, sub.bounds)) { + return sub; + } + } return nil; } diff --git a/ijkmedia/ijksdl/metal/IJKMetalFBO.h b/ijkmedia/ijksdl/metal/IJKMetalFBO.h new file mode 100644 index 0000000000..a20ffdf208 --- /dev/null +++ b/ijkmedia/ijksdl/metal/IJKMetalFBO.h @@ -0,0 +1,29 @@ +// +// IJKMetalFBO.h +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/10. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + + +@protocol MTLRenderCommandEncoder,MTLParallelRenderCommandEncoder,MTLCommandBuffer,MTLDevice; + +@interface IJKMetalFBO : NSObject + +- (instancetype)init:(id)device + size:(CGSize)targetSize; + +- (BOOL)canReuse:(CGSize)size; +- (id)createRenderEncoder:(id)commandBuffer; +- (id)createParallelRenderEncoder:(id)commandBuffer; +- (CGSize)size; +- (CVPixelBufferRef)pixelBuffer; +- (id)texture; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ijkmedia/ijksdl/metal/IJKMetalFBO.m b/ijkmedia/ijksdl/metal/IJKMetalFBO.m new file mode 100644 index 0000000000..dcfc905387 --- /dev/null +++ b/ijkmedia/ijksdl/metal/IJKMetalFBO.m @@ -0,0 +1,170 @@ +// +// IJKMetalFBO.m +// IJKMediaPlayerKit +// +// Created by Reach Matt on 2024/4/10. +// + +#import "IJKMetalFBO.h" +@import Metal; +@import CoreVideo; + +@interface IJKMetalFBO() +{ + CVPixelBufferRef _pixelBuffer; + MTLRenderPassDescriptor* _passDescriptor; +} +@end + +@implementation IJKMetalFBO + +- (void)dealloc +{ + CVPixelBufferRelease(_pixelBuffer); +} + ++ (CVPixelBufferRef)createCVPixelBufferWithSize:(CGSize)size +{ + CVPixelBufferRef pixelBuffer; + NSDictionary* cvBufferProperties = @{ + (__bridge NSString*)kCVPixelBufferMetalCompatibilityKey : @YES, + }; + CVReturn cvret = CVPixelBufferCreate(kCFAllocatorDefault, + size.width, size.height, + kCVPixelFormatType_32BGRA, + (__bridge CFDictionaryRef)cvBufferProperties, + &pixelBuffer); + + + if (cvret == kCVReturnSuccess) { + return pixelBuffer; + } else { + NSAssert(NO, @"Failed to create CVPixelBuffer:%d",cvret); + } + return NULL; +} + +/** + Create a Metal texture from the CoreVideo pixel buffer using the following steps, and as annotated in the code listings below: + */ ++ (id )createMetalTextureFromCVPixelBuffer:(CVPixelBufferRef)pixelBuffer + device:(id)device +{ + CVMetalTextureCacheRef textureCache; + // 1. Create a Metal Core Video texture cache from the pixel buffer. + CVReturn cvret = CVMetalTextureCacheCreate( + kCFAllocatorDefault, + nil, + device, + nil, + &textureCache); + + if (cvret != kCVReturnSuccess) { + NSLog(@"Failed to create Metal texture cache"); + return nil; + } + + // 2. Create a CoreVideo pixel buffer backed Metal texture image from the texture cache. + CVMetalTextureRef texture; + size_t width = (size_t)CVPixelBufferGetWidth(pixelBuffer); + size_t height = (size_t)CVPixelBufferGetHeight(pixelBuffer); + cvret = CVMetalTextureCacheCreateTextureFromImage( + kCFAllocatorDefault, + textureCache, + pixelBuffer, nil, + MTLPixelFormatBGRA8Unorm, + width, height, + 0, + &texture); + + CFRelease(textureCache); + + if (cvret != kCVReturnSuccess) { + NSLog(@"Failed to create CoreVideo Metal texture from image"); + return nil; + } + + // 3. Get a Metal texture using the CoreVideo Metal texture reference. + id metalTexture = CVMetalTextureGetTexture(texture); + + CFRelease(texture); + + if (!metalTexture) { + NSLog(@"Failed to create Metal texture CoreVideo Metal Texture"); + } + + return metalTexture; +} + ++ (MTLRenderPassDescriptor *)renderPassDescriptor:(id)device + pixelBuffer:(CVPixelBufferRef)pixelBuffer +{ + id renderTargetTexture = [self createMetalTextureFromCVPixelBuffer:pixelBuffer device:device]; + MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor new]; + passDescriptor.colorAttachments[0].texture = renderTargetTexture; + passDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; + passDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0); + passDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; + return passDescriptor; +} + +- (BOOL)preparePassDescriptor:(CGSize)size device:(id)device +{ + if (!_passDescriptor) { + // Texture to render to and then sample from. + if (!_pixelBuffer) { + _pixelBuffer = [[self class] createCVPixelBufferWithSize:size]; + } + _passDescriptor = [[self class] renderPassDescriptor:device pixelBuffer:_pixelBuffer]; + } + return !!_passDescriptor; +} + +- (id)createRenderEncoder:(id)commandBuffer +{ + return [commandBuffer renderCommandEncoderWithDescriptor:_passDescriptor]; +} + +- (id)createParallelRenderEncoder:(id)commandBuffer +{ + return [commandBuffer parallelRenderCommandEncoderWithDescriptor:_passDescriptor]; +} + +- (instancetype)init:(id)device + size:(CGSize)targetSize +{ + self = [super init]; + if (self) { + [self preparePassDescriptor:targetSize device:device]; + } + return self; +} + +- (BOOL)canReuse:(CGSize)size +{ + if (_pixelBuffer && _passDescriptor) { + int width = (int)CVPixelBufferGetWidth(_pixelBuffer); + int height = (int)CVPixelBufferGetHeight(_pixelBuffer); + if (width == (int)size.width && height == (int)size.height) { + return YES; + } + } + return NO; +} + +- (CGSize)size +{ + return (CGSize){CVPixelBufferGetWidth(_pixelBuffer),CVPixelBufferGetHeight(_pixelBuffer)}; +} + +- (CVPixelBufferRef)pixelBuffer +{ + return _pixelBuffer; +} + +- (id)texture +{ + return _passDescriptor.colorAttachments[0].texture; +} + +@end diff --git a/ijkmedia/ijksdl/metal/IJKMetalOffscreenRendering.m b/ijkmedia/ijksdl/metal/IJKMetalOffscreenRendering.m index 5999981250..10fbff8bc4 100644 --- a/ijkmedia/ijksdl/metal/IJKMetalOffscreenRendering.m +++ b/ijkmedia/ijksdl/metal/IJKMetalOffscreenRendering.m @@ -6,146 +6,15 @@ // Copyright © 2022 Matt Reach's Awesome FFmpeg Tutotial. All rights reserved. // + #import "IJKMetalOffscreenRendering.h" -@import Metal; -@import CoreVideo; +#import "IJKMetalFBO.h" @import CoreImage; - - -@interface IJKRenderPassDescriptor : NSObject -{ - CVPixelBufferRef _pixelBuffer; - MTLRenderPassDescriptor* _passDescriptor; -} -@end - -@implementation IJKRenderPassDescriptor - -- (void)dealloc -{ - CVPixelBufferRelease(_pixelBuffer); -} - -+ (CVPixelBufferRef)createCVPixelBufferWithSize:(CGSize)size -{ - CVPixelBufferRef pixelBuffer; - NSDictionary* cvBufferProperties = @{ - (__bridge NSString*)kCVPixelBufferOpenGLCompatibilityKey : @YES, - (__bridge NSString*)kCVPixelBufferMetalCompatibilityKey : @YES, - }; - CVReturn cvret = CVPixelBufferCreate(kCFAllocatorDefault, - size.width, size.height, - kCVPixelFormatType_32BGRA, - (__bridge CFDictionaryRef)cvBufferProperties, - &pixelBuffer); - - - if (cvret == kCVReturnSuccess) { - return pixelBuffer; - } else { - NSAssert(NO, @"Failed to create CVPixelBuffer:%d",cvret); - } - return NULL; -} - -/** - Create a Metal texture from the CoreVideo pixel buffer using the following steps, and as annotated in the code listings below: - */ -+ (id )createMetalTextureFromCVPixelBuffer:(CVPixelBufferRef)pixelBuffer - device:(id)device -{ - CVMetalTextureCacheRef textureCache; - // 1. Create a Metal Core Video texture cache from the pixel buffer. - CVReturn cvret = CVMetalTextureCacheCreate( - kCFAllocatorDefault, - nil, - device, - nil, - &textureCache); - - if (cvret != kCVReturnSuccess) { - NSLog(@"Failed to create Metal texture cache"); - return nil; - } - - // 2. Create a CoreVideo pixel buffer backed Metal texture image from the texture cache. - CVMetalTextureRef texture; - size_t width = (size_t)CVPixelBufferGetWidth(pixelBuffer); - size_t height = (size_t)CVPixelBufferGetHeight(pixelBuffer); - cvret = CVMetalTextureCacheCreateTextureFromImage( - kCFAllocatorDefault, - textureCache, - pixelBuffer, nil, - MTLPixelFormatBGRA8Unorm, - width, height, - 0, - &texture); - - CFRelease(textureCache); - - if (cvret != kCVReturnSuccess) { - NSLog(@"Failed to create CoreVideo Metal texture from image"); - return nil; - } - - // 3. Get a Metal texture using the CoreVideo Metal texture reference. - id metalTexture = CVMetalTextureGetTexture(texture); - - CFRelease(texture); - - if (!metalTexture) { - NSLog(@"Failed to create Metal texture CoreVideo Metal Texture"); - } - - return metalTexture; -} - -+ (MTLRenderPassDescriptor *)renderPassDescriptor:(id)device - pixelBuffer:(CVPixelBufferRef)pixelBuffer -{ - id renderTargetTexture = [self createMetalTextureFromCVPixelBuffer:pixelBuffer device:device]; - MTLRenderPassDescriptor *passDescriptor = [MTLRenderPassDescriptor new]; - passDescriptor.colorAttachments[0].texture = renderTargetTexture; - passDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; - passDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 1); - passDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; - return passDescriptor; -} - -- (MTLRenderPassDescriptor *)preparePassDescriptor:(CGSize)size device:(id)device -{ - if (!_passDescriptor) { - // Texture to render to and then sample from. - if (!_pixelBuffer) { - _pixelBuffer = [[self class] createCVPixelBufferWithSize:size]; - } - _passDescriptor = [[self class] renderPassDescriptor:device pixelBuffer:_pixelBuffer]; - } - return _passDescriptor; -} - -- (BOOL)canReuse:(CGSize)size -{ - if (_pixelBuffer && _passDescriptor) { - int width = (int)CVPixelBufferGetWidth(_pixelBuffer); - int height = (int)CVPixelBufferGetHeight(_pixelBuffer); - if (width == (int)size.width && height == (int)size.height) { - return YES; - } - } - return NO; -} - -- (CVPixelBufferRef)pixelBuffer -{ - return _pixelBuffer; -} - -@end +@import Metal; @interface IJKMetalOffscreenRendering () { - IJKRenderPassDescriptor* _passDescriptor; + IJKMetalFBO* _fbo; } @end @@ -153,7 +22,7 @@ @implementation IJKMetalOffscreenRendering - (CGImageRef)_snapshot { - CVPixelBufferRef pixelBuffer = CVPixelBufferRetain([_passDescriptor pixelBuffer]); + CVPixelBufferRef pixelBuffer = CVPixelBufferRetain([_fbo pixelBuffer]); CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer]; static CIContext *context = nil; @@ -173,18 +42,11 @@ - (CGImageRef)snapshot:(CGSize)targetSize commandBuffer:(id)commandBuffer doUploadPicture:(void(^)(id))block { - if (![_passDescriptor canReuse:targetSize]) { - _passDescriptor = [IJKRenderPassDescriptor alloc]; - } - - MTLRenderPassDescriptor * passDescriptor = [_passDescriptor preparePassDescriptor:targetSize device:device]; - - if (!passDescriptor) { - return NULL; + if (![_fbo canReuse:targetSize]) { + _fbo = [[IJKMetalFBO alloc] init:device size:targetSize]; } - id renderEncoder = - [commandBuffer renderCommandEncoderWithDescriptor:passDescriptor]; + id renderEncoder = [_fbo createRenderEncoder:commandBuffer]; if (!renderEncoder) { return NULL; diff --git a/ijkmedia/ijksdl/metal/IJKMetalShaders.metal b/ijkmedia/ijksdl/metal/IJKMetalShaders.metal index a93cdfd41c..ccf7936a93 100644 --- a/ijkmedia/ijksdl/metal/IJKMetalShaders.metal +++ b/ijkmedia/ijksdl/metal/IJKMetalShaders.metal @@ -59,20 +59,31 @@ struct RasterizerData // } //} -/// @brief subtitle bgra fragment shader +/// @brief subtitle direct output fragment shader /// @param stage_in表示这个数据来自光栅化。(光栅化是顶点处理之后的步骤,业务层无法修改) /// @param texture表明是纹理数据,IJKFragmentTextureIndexTextureY 是索引 -fragment float4 subtileFragmentShader(RasterizerData input [[stage_in]], +fragment float4 subtileDIRECTFragment(RasterizerData input [[stage_in]], texture2d textureY [[ texture(IJKFragmentTextureIndexTextureY) ]]) { // sampler是采样器 constexpr sampler textureSampler (mag_filter::linear, min_filter::linear); - //auto converted bgra -> rgba - float4 rgba = textureY.sample(textureSampler, input.textureCoordinate); - return rgba; + return textureY.sample(textureSampler, input.textureCoordinate); +} + +/// @brief subtitle swap r g fragment shader +/// @param stage_in表示这个数据来自光栅化。(光栅化是顶点处理之后的步骤,业务层无法修改) +/// @param texture表明是纹理数据,IJKFragmentTextureIndexTextureY 是索引 +fragment float4 subtileSWAPRGFragment(RasterizerData input [[stage_in]], + texture2d textureY [[ texture(IJKFragmentTextureIndexTextureY) ]]) +{ + // sampler是采样器 + constexpr sampler textureSampler (mag_filter::linear, + min_filter::linear); + return textureY.sample(textureSampler, input.textureCoordinate).bgra; } + #if __METAL_VERSION__ >= 200 struct IJKFragmentShaderArguments { diff --git a/ijkmedia/ijksdl/metal/IJKMetalSubtitlePipeline.h b/ijkmedia/ijksdl/metal/IJKMetalSubtitlePipeline.h index be46cc7de6..d28b431847 100644 --- a/ijkmedia/ijksdl/metal/IJKMetalSubtitlePipeline.h +++ b/ijkmedia/ijksdl/metal/IJKMetalSubtitlePipeline.h @@ -8,18 +8,23 @@ @import MetalKit; NS_ASSUME_NONNULL_BEGIN + +typedef enum : NSUInteger { + IJKMetalSubtitleOutFormatDIRECT, + IJKMetalSubtitleOutFormatSWAP_RB +} IJKMetalSubtitleOutFormat; + NS_CLASS_AVAILABLE(10_13, 11_0) @interface IJKMetalSubtitlePipeline : NSObject - (instancetype)initWithDevice:(id)device - colorPixelFormat:(MTLPixelFormat)colorPixelFormat; + outFormat:(IJKMetalSubtitleOutFormat)outFormat; - (void)lock; - (void)unlock; - (BOOL)createRenderPipelineIfNeed; -- (void)uploadTextureWithEncoder:(id)encoder - texture:(id)subTexture - rect:(CGRect)subRect; +- (void)updateSubtitleVertexIfNeed:(CGRect)rect; +- (void)drawTexture:(id)subTexture encoder:(id)encoder; @end diff --git a/ijkmedia/ijksdl/metal/IJKMetalSubtitlePipeline.m b/ijkmedia/ijksdl/metal/IJKMetalSubtitlePipeline.m index 97dc7c38c8..05a0f4ccb8 100644 --- a/ijkmedia/ijksdl/metal/IJKMetalSubtitlePipeline.m +++ b/ijkmedia/ijksdl/metal/IJKMetalSubtitlePipeline.m @@ -12,7 +12,7 @@ @interface IJKMetalSubtitlePipeline() { id _device; - MTLPixelFormat _colorPixelFormat; + IJKMetalSubtitleOutFormat _outFormat; } // The render pipeline generated from the vertex and fragment shaders in the .metal shader file. @@ -27,13 +27,13 @@ @interface IJKMetalSubtitlePipeline() @implementation IJKMetalSubtitlePipeline - (instancetype)initWithDevice:(id)device - colorPixelFormat:(MTLPixelFormat)colorPixelFormat + outFormat:(IJKMetalSubtitleOutFormat)outFormat { self = [super init]; if (self) { NSAssert(device, @"device can't be nil!"); _device = device; - _colorPixelFormat = colorPixelFormat; + _outFormat = outFormat; _pilelineLock = [[NSLock alloc] init]; } return self; @@ -67,19 +67,28 @@ - (BOOL)createRenderPipelineIfNeed //id defaultLibrary = [device newDefaultLibrary]; id vertexFunction = [defaultLibrary newFunctionWithName:@"subVertexShader"]; NSAssert(vertexFunction, @"can't find subVertexShader Function!"); - id fragmentFunction = [defaultLibrary newFunctionWithName:@"subtileFragmentShader"]; - NSAssert(vertexFunction, @"can't find subtileFragmentShader Function!"); + NSString *fsh = nil; + if (_outFormat == IJKMetalSubtitleOutFormatDIRECT) { + fsh = @"subtileDIRECTFragment"; + } else if (_outFormat == IJKMetalSubtitleOutFormatSWAP_RB) { + fsh = @"subtileSWAPRGFragment"; + } else { + NSAssert(fsh, @"IJKMetalSubtitleOutFormat is wrong!"); + } + id fragmentFunction = [defaultLibrary newFunctionWithName:fsh]; + NSAssert(vertexFunction, @"can't find subtileRGBAFragment Function!"); // Configure a pipeline descriptor that is used to create a pipeline state. MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; pipelineStateDescriptor.vertexFunction = vertexFunction; pipelineStateDescriptor.fragmentFunction = fragmentFunction; - pipelineStateDescriptor.colorAttachments[0].pixelFormat = _colorPixelFormat; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; //important! set subtitle need blending. //https://developer.apple.com/documentation/metal/mtlblendfactor/oneminussourcealpha pipelineStateDescriptor.colorAttachments[0].blendingEnabled = YES; - pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; + //ass字幕已经做了预乘,所以这里选择 MTLBlendFactorOne,而不是 MTLBlendFactorSourceAlpha + pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne; pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha; pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; @@ -130,13 +139,10 @@ - (void)updateSubtitleVertexIfNeed:(CGRect)rect options:MTLResourceStorageModeShared]; // 创建顶点缓存 } -- (void)uploadTextureWithEncoder:(id)encoder - texture:(id)subTexture - rect:(CGRect)subRect +- (void)drawTexture:(id)subTexture encoder:(id)encoder { [encoder setFragmentTexture:subTexture atIndex:IJKFragmentTextureIndexTextureY]; - [self updateSubtitleVertexIfNeed:subRect]; // Pass in the parameter data. [encoder setVertexBuffer:self.vertices offset:0 diff --git a/ijkmedia/ijksdl/metal/IJKMetalView.m b/ijkmedia/ijksdl/metal/IJKMetalView.m index 1e153f7514..82d58342a2 100644 --- a/ijkmedia/ijksdl/metal/IJKMetalView.m +++ b/ijkmedia/ijksdl/metal/IJKMetalView.m @@ -20,7 +20,6 @@ #import "IJKMetalOffscreenRendering.h" #import "ijksdl_vout_ios_gles2.h" -#import "IJKSDLTextureString.h" #import "IJKMediaPlayback.h" #if TARGET_OS_IPHONE @@ -40,13 +39,6 @@ @interface IJKMetalView () @property (atomic, strong) IJKMetalSubtitlePipeline *subPipeline; @property (nonatomic, strong) IJKMetalOffscreenRendering *offscreenRendering; @property (atomic, strong) IJKOverlayAttach *currentAttach; -@property(atomic) BOOL subtitlePreferenceChanged; -//display window size / video size -@property(atomic) float displayVideoScale; -//display window size / screen size -@property(atomic) float subtitleExtScale; -//window's backingScaleFactor -@property(atomic) float backingScaleFactor; @property(assign) int hdrAnimationFrameCount; @end @@ -54,11 +46,9 @@ @interface IJKMetalView () @implementation IJKMetalView @synthesize scalingMode = _scalingMode; -// subtitle preference -@synthesize subtitlePreference = _subtitlePreference; // rotate preference @synthesize rotatePreference = _rotatePreference; -// color conversion perference +// color conversion preference @synthesize colorPreference = _colorPreference; // user defined display aspect ratio @synthesize darPreference = _darPreference; @@ -69,20 +59,6 @@ @implementation IJKMetalView #endif @synthesize showHdrAnimation = _showHdrAnimation; -- (CGSize)screenSize -{ - static CGSize screenSize; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - #if TARGET_OS_OSX - screenSize = [[[NSScreen screens] firstObject]frame].size; - #else - screenSize = [[UIScreen mainScreen]bounds].size; - #endif - }); - return screenSize; -} - - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; @@ -91,12 +67,9 @@ - (void)dealloc - (BOOL)prepareMetal { - _subtitlePreference = (IJKSDLSubtitlePreference){1.0, 0xFFFFFF, 0.1}; _rotatePreference = (IJKSDLRotatePreference){IJKSDLRotateNone, 0.0}; _colorPreference = (IJKSDLColorConversionPreference){1.0, 1.0, 1.0}; _darPreference = (IJKSDLDARPreference){0.0}; - _displayVideoScale = 1.0; - _subtitleExtScale = 1.0; self.device = MTLCreateSystemDefaultDevice(); if (!self.device) { @@ -154,22 +127,6 @@ - (void)setShowHdrAnimation:(BOOL)showHdrAnimation } } -- (void)videoNaturalSizeChanged:(CGSize)size -{ - CGSize viewSize = [self bounds].size; - self.displayVideoScale = FFMIN(1.0 * viewSize.width / size.width, 1.0 * viewSize.height / size.height); -} - -- (void)cleanSubtitle -{ - IJKOverlayAttach * attach = self.currentAttach; - if (attach && attach.subTexture) { - attach.sub = nil; - attach.subTexture = nil; - [self setNeedsRefreshCurrentPic]; - } -} - - (CGSize)computeNormalizedVerticesRatio:(IJKOverlayAttach *)attach { if (_scalingMode == IJKMPMovieScalingModeFill) { @@ -231,7 +188,7 @@ - (CGSize)computeNormalizedVerticesRatio:(IJKOverlayAttach *)attach - (BOOL)setupSubPipelineIfNeed { if (!self.subPipeline) { - self.subPipeline = [[IJKMetalSubtitlePipeline alloc] initWithDevice:self.device colorPixelFormat:self.colorPixelFormat]; + self.subPipeline = [[IJKMetalSubtitlePipeline alloc] initWithDevice:self.device outFormat:IJKMetalSubtitleOutFormatDIRECT]; } if ([self.subPipeline createRenderPipelineIfNeed]) { @@ -294,16 +251,28 @@ - (void)encodePicture:(IJKOverlayAttach *)attach - (void)encodeSubtitle:(id)renderEncoder viewport:(CGSize)viewport - texture:(id)subTexture - rect:(CGRect)subRect + texture:(id)subTexture { [self.subPipeline lock]; // Set the region of the drawable to draw into. [renderEncoder setViewport:(MTLViewport){0.0, 0.0, viewport.width, viewport.height, -1.0, 1.0}]; //upload textures - [self.subPipeline uploadTextureWithEncoder:renderEncoder - texture:subTexture - rect:subRect]; + + float wRatio = viewport.width / subTexture.width; + float hRatio = viewport.height / subTexture.height; + + CGRect subRect; + //aspect fit + if (wRatio < hRatio) { + float nH = (subTexture.height * wRatio / viewport.height); + subRect = CGRectMake(-1, -nH, 2.0, 2.0 * nH); + } else { + float nW = (subTexture.width * hRatio / viewport.width); + subRect = CGRectMake(-nW, -1, 2.0 * nW, 2.0); + } + + [self.subPipeline updateSubtitleVertexIfNeed:subRect]; + [self.subPipeline drawTexture:subTexture encoder:renderEncoder]; [self.subPipeline unlock]; } @@ -363,25 +332,9 @@ - (void)drawRect:(NSRect)dirtyRect hdrPercentage:hdrPer]; if (attach.subTexture) { - float subScale = 1.0; - if (attach.sub.pixels) { - subScale = self.subtitlePreference.ratio * self.displayVideoScale * 1.5; - } - //保证 Retina 屏幕显示的大小和非 Retina 屏幕上一样大 - /* - 为何,截图时有的乘以 backingScaleFactor ,有的不乘? - 当 viewport 是 retina 之后的像素值时,就需要乘,使得字幕也跟着变大; - 反之,单倍屏上,直接显示就可以了。 - */ - subScale *= self.backingScaleFactor; - //实现,窗口放大,字幕放大效果 - subScale *= self.subtitleExtScale; - CGRect rect = [self subTextureTargetRect:attach.subTexture scale:subScale viewport:viewport]; - [self encodeSubtitle:renderEncoder viewport:viewport - texture:attach.subTexture - rect:rect]; + texture:attach.subTexture]; } [renderEncoder popDebugGroup]; [renderEncoder endEncoding]; @@ -455,25 +408,9 @@ - (CGImageRef)_snapshotWithSubtitle:(BOOL)drawSub hdrPercentage:1.0]; if (drawSub && attach.subTexture) { - float subScale = 1.0; - - if (attach.sub.pixels) { - subScale = self.subtitlePreference.ratio * self.displayVideoScale * 1.5; - } - - { - CGSize screenSize = [self screenSize]; - CGSize viewSize = viewport; - //当前显示窗口相对屏幕的比例;实际上全屏时是1,非全屏小于1; - float subtitleExtScale = FFMIN(1.0 * viewSize.width / screenSize.width, 1.0 * viewSize.height / screenSize.height); - subScale *= subtitleExtScale; - } - - CGRect rect = [self subTextureTargetRect:attach.subTexture scale:subScale viewport:viewport]; [self encodeSubtitle:renderEncoder viewport:viewport - texture:attach.subTexture - rect:rect]; + texture:attach.subTexture]; } }]; } @@ -534,23 +471,9 @@ - (CGImageRef)_snapshotScreen } if (attach.subTexture) { - float subScale = 1.0; - if (attach.sub.pixels) { - subScale = self.subtitlePreference.ratio * self.displayVideoScale * 1.5; - } - - float subtitleExtScale = [self computeSubtitleExtSacle]; - subScale *= subtitleExtScale; - - //viewport 取的是 retina 相关的,字幕需要等比例放大。 - subScale *= self.backingScaleFactor; - - CGRect rect = [self subTextureTargetRect:attach.subTexture scale:subScale viewport:viewport]; - [self encodeSubtitle:renderEncoder viewport:viewport - texture:attach.subTexture - rect:rect]; + texture:attach.subTexture]; } }]; } @@ -569,24 +492,6 @@ - (CGImageRef)snapshot:(IJKSDLSnapshotType)aType } } -- (float)computeSubtitleExtSacle -{ - CGSize screenSize = [self screenSize]; - CGSize viewSize = [self bounds].size; - //当前显示窗口相对屏幕的比例;实际上全屏时是1,非全屏小于1; - return FFMIN(1.0 * viewSize.width / screenSize.width, 1.0 * viewSize.height / screenSize.height); -} - -- (void)refreshSubtitleExtSacle -{ -#if TARGET_OS_IOS - self.backingScaleFactor = self.window.contentScaleFactor; -#else - self.backingScaleFactor = self.window.backingScaleFactor; -#endif - self.subtitleExtScale = [self computeSubtitleExtSacle]; -} - #if TARGET_OS_IOS - (UIImage *)snapshot { @@ -594,17 +499,10 @@ - (UIImage *)snapshot return [[UIImage alloc]initWithCGImage:cgImg]; } -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - [self refreshSubtitleExtSacle]; -} - #else - (void)windowDidEndLiveResize:(NSNotification *)notifi { - [self refreshSubtitleExtSacle]; if (notifi.object == self.window) { [self setNeedsRefreshCurrentPic]; } @@ -614,7 +512,6 @@ - (void)resizeWithOldSuperviewSize:(NSSize)oldSize { //call super is needed, otherwise some device [self bounds] is not right. [super resizeWithOldSuperviewSize:oldSize]; - [self refreshSubtitleExtSacle]; if (!self.window.inLiveResize) { [self setNeedsRefreshCurrentPic]; } @@ -625,105 +522,15 @@ - (void)viewDidChangeBackingProperties [super viewDidChangeBackingProperties]; //多显示器间切换,drawable还没来得及自动改变,因此先手动调整好;避免由于viewport不对导致字幕显示过大或过小。 self.drawableSize = [self convertSizeToBacking:self.bounds.size]; - [self refreshSubtitleExtSacle]; [self setNeedsRefreshCurrentPic]; } #endif - (void)setNeedsRefreshCurrentPic { - if (self.subtitlePreferenceChanged) { - self.subtitlePreferenceChanged = NO; - [self generateSubTexture:self.currentAttach]; - } [self draw]; } -- (CVPixelBufferRef)_generateSubtitlePixel:(NSString *)subtitle videoDegrees:(int)degrees -{ - if (subtitle.length == 0) { - return NULL; - } - - IJKSDLSubtitlePreference sp = self.subtitlePreference; - - float ratio = sp.ratio; - int32_t bgrValue = sp.color; - //以800为标准,定义出字幕字体默认大小为30pt - float scale = 1.0; - CGSize screenSize = [self screenSize]; - if (degrees / 90 % 2 == 1) { - scale = screenSize.height / 800.0; - } else { - scale = screenSize.width / 800.0; - } - //字幕默认配置 - NSMutableDictionary * attributes = [[NSMutableDictionary alloc] init]; - - UIFont *subtitleFont = [UIFont systemFontOfSize:ratio * scale * 30]; - [attributes setObject:subtitleFont forKey:NSFontAttributeName]; - - NSColor *subtitleColor = [NSColor colorWithRed:((float)(bgrValue & 0xFF)) / 255.0 green:((float)((bgrValue & 0xFF00) >> 8)) / 255.0 blue:(float)(((bgrValue & 0xFF0000) >> 16)) / 255.0 alpha:1.0]; - - [attributes setObject:subtitleColor forKey:NSForegroundColorAttributeName]; - - IJKSDLTextureString *textureString = [[IJKSDLTextureString alloc] initWithString:subtitle withAttributes:attributes]; - - return [textureString createPixelBuffer]; -} - -- (CVPixelBufferRef)_generateSubtitlePixelFromPicture:(IJKSDLSubtitle*)pict -{ - CVPixelBufferRef pixelBuffer = NULL; - NSDictionary *options = @{ - (__bridge NSString*)kCVPixelBufferMetalCompatibilityKey : @YES, - (__bridge NSString*)kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary] - }; - - CVReturn ret = CVPixelBufferCreate(kCFAllocatorDefault, pict.w, pict.h, kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef)options, &pixelBuffer); - - if (ret != kCVReturnSuccess || pixelBuffer == NULL) { - ALOGE("CVPixelBufferCreate subtitle failed:%d",ret); - return NULL; - } - - CVPixelBufferLockBaseAddress(pixelBuffer, 0); - - uint8_t *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer); - int linesize = (int)CVPixelBufferGetBytesPerRow(pixelBuffer); - - uint8_t *dst_data[4] = {baseAddress,NULL,NULL,NULL}; - int dst_linesizes[4] = {linesize,0,0,0}; - - const uint8_t *src_data[4] = {pict.pixels,NULL,NULL,NULL}; - const int src_linesizes[4] = {pict.w * 4,0,0,0}; - - av_image_copy(dst_data, dst_linesizes, src_data, src_linesizes, AV_PIX_FMT_BGRA, pict.w, pict.h); - - CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); - - if (kCVReturnSuccess == ret) { - return pixelBuffer; - } else { - return NULL; - } -} - -- (void)generateSubTexture:(IJKOverlayAttach *)attach -{ - CVPixelBufferRef subRef = NULL; - IJKSDLSubtitle *sub = attach.sub; - if (sub) { - if (sub.text.length > 0) { - subRef = [self _generateSubtitlePixel:sub.text videoDegrees:attach.autoZRotate]; - } else if (sub.pixels != NULL) { - subRef = [self _generateSubtitlePixelFromPicture:sub]; - } - } - attach.subTexture = [[self class] doGenerateSubTexture:subRef device:self.device]; - CVPixelBufferRelease(subRef); -} - mp_format * mp_get_metal_format(uint32_t cvpixfmt); + (NSArray> *)doGenerateTexture:(CVPixelBufferRef)pixelBuffer @@ -766,106 +573,25 @@ - (void)generateSubTexture:(IJKOverlayAttach *)attach return result; } -+ (id)doGenerateSubTexture:(CVPixelBufferRef)pixelBuff - device:(id)device -{ - if (!pixelBuff) { - return nil; - } - - OSType type = CVPixelBufferGetPixelFormatType(pixelBuff); - if (type != kCVPixelFormatType_32BGRA) { - ALOGE("generate subtitle texture must use 32BGRA pixelBuff"); - return nil; - } - - CVPixelBufferLockBaseAddress(pixelBuff, kCVPixelBufferLock_ReadOnly); - void *src = CVPixelBufferGetBaseAddress(pixelBuff); - - MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init]; - - // Indicate that each pixel has a blue, green, red, and alpha channel, where each channel is - // an 8-bit unsigned normalized value (i.e. 0 maps to 0.0 and 255 maps to 1.0) - textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm; - - // Set the pixel dimensions of the texture - - textureDescriptor.width = CVPixelBufferGetWidth(pixelBuff); - textureDescriptor.height = CVPixelBufferGetHeight(pixelBuff); - - // Create the texture from the device by using the descriptor - id subTexture = [device newTextureWithDescriptor:textureDescriptor]; - - MTLRegion region = { - { 0, 0, 0 }, // MTLOrigin - {CVPixelBufferGetWidth(pixelBuff), CVPixelBufferGetHeight(pixelBuff), 1} // MTLSize - }; - - NSUInteger bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuff); - - [subTexture replaceRegion:region - mipmapLevel:0 - withBytes:src - bytesPerRow:bytesPerRow]; - - CVPixelBufferUnlockBaseAddress(pixelBuff, kCVPixelBufferLock_ReadOnly); - - return subTexture; -} - -- (CGRect)subTextureTargetRect:(id)subTexture - scale:(float)scale - viewport:(CGSize)viewport -{ - float bottomMargin = self.subtitlePreference.bottomMargin; - - //没有这个scale的话,字幕可能会超出画面,位置跟观看时不一致。 - float swidth = subTexture.width * scale; - float sheight = subTexture.height * scale; - - float width = viewport.width; - float height = viewport.height; - //转化到 [-1,1] 的区间 - float y = bottomMargin * (height - sheight) / height * 2.0 - 1.0; - - if (width != 0 && height != 0) { - return (CGRect){ - - 1.0 * swidth / width, - y, - 2.0 * (swidth / width), - 2.0 * (sheight / height) - }; - } - return CGRectZero; -} - - (BOOL)displayAttach:(IJKOverlayAttach *)attach { - if (!attach) { - ALOGW("IJKMetalView: overlay is nil\n"); - return NO; - } - - if (self.subtitlePreferenceChanged || self.currentAttach.sub != attach.sub) { - [self generateSubTexture:attach]; - } else if (self.currentAttach.sub) { - //reuse the expensive texture. - attach.subTexture = self.currentAttach.subTexture; - } + //hold the attach as current. + self.currentAttach = attach; - if (self.subtitlePreferenceChanged) { - self.subtitlePreferenceChanged = NO; + if (!attach.videoPicture) { + ALOGW("IJKMetalView: videiPicture is nil\n"); + return NO; } - //hold the attach as current. - self.currentAttach = attach; + attach.videoTextures = [[self class] doGenerateTexture:attach.videoPicture textureCache:_pictureTextureCache]; if (self.preventDisplay) { return YES; } - attach.videoTextures = [[self class] doGenerateTexture:attach.videoPicture textureCache:_pictureTextureCache]; - + if (CGSizeEqualToSize(CGSizeZero, self.drawableSize)) { + return NO; + } //not dispatch to main thread, use current sub thread (ff_vout) draw [self draw]; @@ -902,22 +628,17 @@ - (void)setDarPreference:(IJKSDLDARPreference)darPreference } } -- (void)setSubtitlePreference:(IJKSDLSubtitlePreference)subtitlePreference -{ - if (_subtitlePreference.ratio != subtitlePreference.ratio || - _subtitlePreference.color != subtitlePreference.color || - _subtitlePreference.bottomMargin != subtitlePreference.bottomMargin) { - _subtitlePreference = subtitlePreference; - self.subtitlePreferenceChanged = YES; - } -} - - (void)setBackgroundColor:(uint8_t)r g:(uint8_t)g b:(uint8_t)b { self.clearColor = (MTLClearColor){r/255.0, g/255.0, b/255.0, 1.0f}; //renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0f); } +- (id)context +{ + return self.device; +} + - (NSString *)name { return @"Metal"; @@ -927,6 +648,13 @@ - (NSString *)name - (NSView *)hitTest:(NSPoint)point { + for (NSView *sub in [self subviews]) { + NSPoint pointInSelf = [self convertPoint:point fromView:self.superview]; + NSPoint pointInSub = [self convertPoint:pointInSelf toView:sub]; + if (NSPointInRect(pointInSub, sub.bounds)) { + return sub; + } + } return nil; } diff --git a/ijkmedia/tools/mr_stream_component.c b/ijkmedia/tools/mr_stream_component.c new file mode 100644 index 0000000000..57c06d6511 --- /dev/null +++ b/ijkmedia/tools/mr_stream_component.c @@ -0,0 +1,445 @@ +// +// mr_stream_component.c +// +// ijkplayer not use the file, but the file will be used by other module in app. +// +// Created by Reach Matt on 2023/9/7. +// + +#include "mr_stream_component.h" +#include "ff_frame_queue.h" +#include "ff_packet_list.h" + +typedef struct MRStreamComponent{ + PacketQueue* packetq; + Decoder decoder; + FrameQueue* frameq; + AVFormatContext *ic; + int64_t seek_req; + AVPacket *pkt; + int st_idx; + int eof; +}MRStreamComponent; + +static int stream_has_enough_packets(PacketQueue *queue, int min_frames) +{ + return queue->abort_request || queue->nb_packets > min_frames; +} + +static int read_enough_packets(MRStreamComponent *sc) +{ + if (!sc || sc->eof || !sc->ic) { + goto end; + } + + do { + if (stream_has_enough_packets(sc->packetq, 5)) { + break; + } + sc->pkt->flags = 0; + int ret = av_read_frame(sc->ic, sc->pkt); + if (ret >= 0) { + if (sc->pkt->stream_index != sc->st_idx) { + av_packet_unref(sc->pkt); + } else { + packet_queue_put(sc->packetq, sc->pkt); + } + continue; + } else if (ret == AVERROR_EOF) { + packet_queue_put_nullpacket(sc->packetq, sc->pkt, sc->st_idx); + sc->eof = 1; + break; + } else { + break; + } + } while (sc->packetq->abort_request == 0); + +end: + return stream_has_enough_packets(sc->packetq, 0); +} + +static int fetch_a_packet(MRStreamComponent *sc, Decoder *d) +{ + while (sc->packetq->abort_request == 0) { + if (sc->seek_req >= 0) { + av_log(NULL, AV_LOG_DEBUG,"sub seek to:%lld\n",fftime_to_seconds(sc->seek_req)); + if (avformat_seek_file(sc->ic, -1, INT64_MIN, sc->seek_req, INT64_MAX, 0) < 0) { + av_log(NULL, AV_LOG_WARNING, "%d: could not seek to position %lld\n", + sc->st_idx, sc->seek_req); + sc->seek_req = -1; + return -2; + } + sc->seek_req = -1; + packet_queue_flush(sc->packetq); + continue; + } + + int r = packet_queue_get(d->queue, d->pkt, 0, &d->pkt_serial); + if (r < 0) { + return -1; + } else if (r == 0) { + if (read_enough_packets(sc) > 0) { + continue; + } else { + av_usleep(1000 * 3); + } + } else { + return 0; + } + } + return -3; +} + +static int decoder_decode_frame(MRStreamComponent *sc, AVFrame *frame, AVSubtitle *sub) { + + Decoder *d = &sc->decoder; + int status = 0; + + for (;sc->packetq->abort_request == 0;) { + + if (d->queue->serial == d->pkt_serial) { + + int ret = AVERROR(EAGAIN); + do { + if (d->queue->abort_request) { + status = -1; + goto abort_end; + } + + switch (d->avctx->codec_type) { + case AVMEDIA_TYPE_VIDEO: + ret = avcodec_receive_frame(d->avctx, frame); + if (ret >= 0) { + frame->pts = frame->best_effort_timestamp; + if (frame->format == AV_PIX_FMT_VIDEOTOOLBOX) { + /* retrieve data from GPU to CPU */ + AVFrame *sw_frame = av_frame_alloc(); + //use av_hwframe_map instead of av_hwframe_transfer_data + if ((ret = av_hwframe_map(sw_frame, frame, 0)) < 0) { + av_log(NULL, AV_LOG_ERROR, "Error transferring the data to system memory\n"); + } + av_frame_unref(frame); + av_frame_move_ref(frame, sw_frame); + } + } + break; + case AVMEDIA_TYPE_AUDIO: + { + ret = avcodec_receive_frame(d->avctx, frame); + if (ret >= 0) { + AVRational tb = (AVRational){1, frame->sample_rate}; + if (frame->pts != AV_NOPTS_VALUE) + frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb); + else if (d->next_pts != AV_NOPTS_VALUE) + frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb); + if (frame->pts != AV_NOPTS_VALUE) { + d->next_pts = frame->pts + frame->nb_samples; + d->next_pts_tb = tb; + } + } + } + break; + default: + break; + } + + if (ret >= 0) { + status = 1; + goto abort_end; + } else if (ret == AVERROR_EOF) { + d->finished = d->pkt_serial; + avcodec_flush_buffers(d->avctx); + status = 0; + goto abort_end; + } + } while (ret != AVERROR(EAGAIN)); + } else { + if (d->queue->abort_request) { + status = -1; + goto abort_end; + } + } + + do { + if (d->packet_pending) { + d->packet_pending = 0; + } else { + int old_serial = d->pkt_serial; + if (fetch_a_packet(sc, d) < 0) { + status = -2; + goto abort_end; + } + if (old_serial != d->pkt_serial) { + avcodec_flush_buffers(d->avctx); + d->finished = 0; + d->hw_failed_count = 0; + d->next_pts = d->start_pts; + d->next_pts_tb = d->start_pts_tb; + } + } + if (d->queue->serial == d->pkt_serial) + break; + av_packet_unref(d->pkt); + } while (sc->packetq->abort_request == 0); + + if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) { + int got_frame = 0; + int ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, d->pkt); + if (ret < 0) { + ret = AVERROR(EAGAIN); + } else { + if (got_frame && !d->pkt->data) { + d->packet_pending = 1; + } + ret = got_frame ? 0 : (d->pkt->data ? AVERROR(EAGAIN) : AVERROR_EOF); + } + if (ret >= 0) { + status = 1; + goto abort_end; + } else if (ret == AVERROR_EOF) { + d->finished = d->pkt_serial; + avcodec_flush_buffers(d->avctx); + status = 0; + goto abort_end; + } + av_packet_unref(d->pkt); + } else { + if (d->queue->abort_request){ + status = -1; + goto abort_end; + } + int send = avcodec_send_packet(d->avctx, d->pkt); + if (send == AVERROR(EAGAIN)) { + av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n"); + d->packet_pending = 1; + } else { + av_packet_unref(d->pkt); + + if (send != 0) { + char errbuf[128] = { '\0' }; + av_strerror(send, errbuf, sizeof(errbuf)); + av_log(d->avctx, AV_LOG_ERROR, "avcodec_send_packet failed:%s(%d).\n",errbuf,send); + } + } + } + + } + +abort_end: + if (d->queue->abort_request && status == -1) { + av_log(NULL, AV_LOG_INFO, "will destroy avcodec:%d,flush buffers.\n",d->avctx->codec_type); + avcodec_send_packet(d->avctx, NULL); + avcodec_flush_buffers(d->avctx); + } + return status; +} + +static int get_video_frame(MRStreamComponent *sc, AVFrame *frame) +{ + return decoder_decode_frame(sc, frame, NULL); +} + +static int get_audio_frame(MRStreamComponent *sc, AVFrame *frame) +{ + return decoder_decode_frame(sc, frame, NULL); +} + +static int get_subtitle_frame(MRStreamComponent *sc, AVSubtitle *pkt) +{ + return decoder_decode_frame(sc, NULL, pkt); +} + +static int sub_component_thread(void *arg) +{ + MRStreamComponent *sc = arg; + int ret = 0; + AVFrame *frame = av_frame_alloc(); + for (;sc->packetq->abort_request == 0;) { + + switch (sc->decoder.avctx->codec_type) { + case AVMEDIA_TYPE_VIDEO: { + if (get_video_frame(sc, frame) > 0) { + AVStream *stream = sc->ic->streams[sc->st_idx]; + AVRational tb = stream->time_base; + AVRational frame_rate = av_guess_frame_rate(sc->ic, stream, NULL); + double duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0); + double pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); + + Frame *sp = frame_queue_peek_writable(sc->frameq); + if (!sp) + return 0; + + sp->pos = frame->pkt_pos; + sp->pts = pts; + sp->duration = duration; + sp->serial = sc->decoder.pkt_serial; + sp->sar = frame->sample_aspect_ratio; + av_frame_move_ref(sp->frame, frame); + frame_queue_push(sc->frameq); + } else { + av_usleep(10); + ret = -1; + } + break; + } + break; + case AVMEDIA_TYPE_AUDIO: { + if (get_audio_frame(sc, frame) > 0) { + AVRational tb = (AVRational){1, frame->sample_rate}; + + Frame *sp = frame_queue_peek_writable(sc->frameq); + if (!sp) + return 0; + sp->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); + sp->pos = frame->pkt_pos; + sp->serial = sc->decoder.pkt_serial; + sp->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate}); + av_frame_move_ref(sp->frame, frame); + frame_queue_push(sc->frameq); + } else { + av_usleep(10); + ret = -1; + } + break; + } + case AVMEDIA_TYPE_SUBTITLE: { + Frame *sp = frame_queue_peek_writable(sc->frameq); + if (!sp) + return 0; + if (get_subtitle_frame(sc, &sp->sub) > 0) { + + int serial = sc->decoder.pkt_serial; + if (sc->packetq->serial == serial) { + Frame *sp = frame_queue_peek_writable(sc->frameq); + if (!sp) + return 0; + + double pts = 0; + if (sp->sub.pts != AV_NOPTS_VALUE) + pts = sp->sub.pts / (double)AV_TIME_BASE; + sp->pts = pts; + sp->serial = serial; + sp->width = sc->decoder.avctx->width; + sp->height = sc->decoder.avctx->height; + sp->shown = 0; + frame_queue_push(sc->frameq); + //av_log(NULL, AV_LOG_DEBUG,"sub received frame:%f\n",pts); + } else { + av_log(NULL, AV_LOG_DEBUG,"sub stream push old frame:%d\n",serial); + } + } else { + av_usleep(10); + ret = -1; + } + break; + } + default: + { + ret = -1; + } + break; + } + } + av_frame_free(&frame); + return ret; +} + +int streamComponent_open(MRStreamComponent **scp, int stream_index, AVFormatContext* ic, AVCodecContext *avctx, PacketQueue* packetq, FrameQueue* frameq) +{ + if (!scp) { + return -1; + } + + MRStreamComponent *sc = av_mallocz(sizeof(MRStreamComponent)); + if (!sc) { + return -2; + } + + assert(frameq); + assert(packetq); + + sc->frameq = frameq; + sc->packetq = packetq; + sc->seek_req = -1; + sc->ic = ic; + sc->pkt = av_packet_alloc(); + sc->eof = 0; + int ret = decoder_init(&sc->decoder, avctx, sc->packetq, NULL); + + if (ret < 0) { + av_free(sc); + return ret; + } + + ret = decoder_start(&sc->decoder, sub_component_thread, sc, "ff_sc_dec"); + if (ret < 0) { + decoder_destroy(&sc->decoder); + av_free(sc); + return ret; + } + sc->st_idx = stream_index; + av_log(NULL, AV_LOG_DEBUG, "sub stream opened:%d,serial:%d\n",stream_index,packetq->serial); + *scp = sc; + return 0; +} + +int streamComponent_close(MRStreamComponent **scp) +{ + if (!scp) { + return -1; + } + + MRStreamComponent *sub = *scp; + if (!sub) { + return -2; + } + + if (sub->st_idx == -1) { + return -3; + } + + decoder_abort(&sub->decoder, sub->frameq); + decoder_destroy(&sub->decoder); + av_packet_free(&sub->pkt); + av_log(NULL, AV_LOG_DEBUG, "sub stream closed:%d\n",sub->st_idx); + sub->st_idx = -1; + av_freep(scp); + return 0; +} + +int streamComponent_get_stream(MRStreamComponent *sc) +{ + if (sc) { + return sc->st_idx; + } + return -1; +} + +int streamComponent_seek_to(MRStreamComponent *sc, int sec) +{ + if (!sc || !sc->ic) { + return -1; + } + if (sec < 0) { + sec = 0; + } + sc->seek_req = seconds_to_fftime(sec); + sc->eof = 0; + return 0; +} + +int streamComponent_get_pkt_serial(MRStreamComponent *sc) +{ + if (!sc || !sc->ic) { + return -1; + } + return sc->decoder.pkt_serial; +} + +int streamComponent_eof_and_pkt_empty(MRStreamComponent *sc) +{ + if (!sc) { + return -1; + } + return sc->eof && sc->decoder.finished == sc->packetq->serial && frame_queue_nb_remaining(sc->frameq) == 0; +} diff --git a/ijkmedia/tools/mr_stream_component.h b/ijkmedia/tools/mr_stream_component.h new file mode 100644 index 0000000000..6e2ae1d304 --- /dev/null +++ b/ijkmedia/tools/mr_stream_component.h @@ -0,0 +1,29 @@ +// +// mr_stream_component.h +// +// ijkplayer not use the file, but the file will be used by other module in app. +// +// Created by Reach Matt on 2023/9/7. +// + +#ifndef mr_stream_component_h +#define mr_stream_component_h + +#include + +typedef struct MRStreamComponent MRStreamComponent; +typedef struct AVStream AVStream; +typedef struct AVCodecContext AVCodecContext; +typedef struct PacketQueue PacketQueue; +typedef struct FrameQueue FrameQueue; +typedef struct AVFormatContext AVFormatContext; + +//when hasn't ic, not support seek; +int streamComponent_open(MRStreamComponent **scp, int stream_index, AVFormatContext* ic, AVCodecContext *avctx, PacketQueue* packetq, FrameQueue* frameq); +int streamComponent_close(MRStreamComponent **scp); +int streamComponent_get_stream(MRStreamComponent *sc); +int streamComponent_seek_to(MRStreamComponent *sc, int sec); +int streamComponent_get_pkt_serial(MRStreamComponent *sc); +int streamComponent_eof_and_pkt_empty(MRStreamComponent *sc); + +#endif /* mr_stream_component_h */ diff --git a/ijkmedia/tools/mr_stream_peek.c b/ijkmedia/tools/mr_stream_peek.c new file mode 100644 index 0000000000..beca9505ad --- /dev/null +++ b/ijkmedia/tools/mr_stream_peek.c @@ -0,0 +1,458 @@ +// +// mr_stream_peek.c +// +// ijkplayer not use the file, but the file will be used by other module in app. +// +// Created by Reach Matt on 2023/9/7. +// + +#include "mr_stream_peek.h" +#include "mr_stream_component.h" +#include "ff_frame_queue.h" +#include "ff_packet_list.h" +#include "ff_ffplay_def.h" +#include "ff_ffplay_debug.h" +#include +#include + +#define MRSampleFormat AV_SAMPLE_FMT_S16P +#define MRSampleRate 16000 +#define MRNBChannels 1 + +typedef struct MRStreamPeeker { + SDL_mutex* mutex; + MRStreamComponent* opaque; + AVFormatContext* ic; + int stream_idx; + PacketQueue pktq; + FrameQueue frameq; + + struct SwrContext *swr_ctx; + struct AudioParams audio_src; + + int audio_buf_index; + int audio_buf_size; + uint8_t *audio_buf; + uint8_t *audio_buf1; + unsigned int audio_buf1_size; + double audio_clock; + int audio_clock_serial; + //video duration + int duration; +}MRStreamPeeker; + +int mr_stream_peek_create(MRStreamPeeker **spp,int frameCacheCount) +{ + if (!spp) { + return -1; + } + + MRStreamPeeker *sp = av_malloc(sizeof(MRStreamPeeker)); + if (!sp) { + return -2; + } + bzero(sp, sizeof(MRStreamPeeker)); + + sp->mutex = SDL_CreateMutex(); + if (NULL == sp->mutex) { + av_free(sp); + return -2; + } + + if (packet_queue_init(&sp->pktq) < 0) { + av_free(sp); + return -3; + } + + if (frame_queue_init(&sp->frameq, &sp->pktq, frameCacheCount, 0) < 0) { + packet_queue_destroy(&sp->pktq); + av_free(sp); + return -4; + } + + sp->stream_idx = -1; + *spp = sp; + return 0; +} + +int mr_stream_peek_get_opened_stream_idx(MRStreamPeeker *sp) +{ + if (sp && sp->opaque) { + return sp->stream_idx; + } + return -1; +} + +int mr_stream_peek_seek_to(MRStreamPeeker *sp, float sec) +{ + if (!sp || !sp->opaque) { + return -1; + } + return streamComponent_seek_to(sp->opaque, sec); +} + +//FILE *file_pcm_l = NULL; +static int audio_decode_frame(MRStreamPeeker *sp) +{ + if (sp->pktq.abort_request) + return -1; + + Frame *af; + + //skip old audio frames. + do { + af = frame_queue_peek_readable_noblock(&sp->frameq); + if (af == NULL) { + if (streamComponent_eof_and_pkt_empty(sp->opaque)) { + return -1; + } else { + av_usleep(10); + } + } else { + if (af->serial != sp->pktq.serial) { + frame_queue_next(&sp->frameq); + continue; + } else { + break; + } + } + } while (1); + + AVFrame *frame = af->frame; + + static int flag = 1; + + if (flag) { + av_log(NULL, AV_LOG_WARNING, "audio sample rate:%d\n",frame->sample_rate); + av_log(NULL, AV_LOG_WARNING, "audio format:%s\n",av_get_sample_fmt_name(frame->format)); + flag = 0; + } + + int need_convert = frame->format != sp->audio_src.fmt || + av_channel_layout_compare(&frame->ch_layout, &sp->audio_src.ch_layout) || + frame->sample_rate != sp->audio_src.freq || + !sp->swr_ctx; + + if (need_convert) { + swr_free(&sp->swr_ctx); + AVChannelLayout layout; + av_channel_layout_default(&layout, MRNBChannels); + swr_alloc_set_opts2(&sp->swr_ctx, + &layout, MRSampleFormat, MRSampleRate, + &frame->ch_layout, frame->format, frame->sample_rate, + 0, NULL); + if (!sp->swr_ctx) { + av_log(NULL, AV_LOG_ERROR, + "swr_alloc_set_opts2 failed!\n"); + return -1; + } + + if (swr_init(sp->swr_ctx) < 0) { + av_log(NULL, AV_LOG_ERROR, + "Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!\n", + frame->sample_rate, av_get_sample_fmt_name(frame->format), frame->ch_layout.nb_channels, + MRSampleRate, av_get_sample_fmt_name(MRSampleFormat), layout.nb_channels); + swr_free(&sp->swr_ctx); + return -1; + } + + if (av_channel_layout_copy(&sp->audio_src.ch_layout, &frame->ch_layout) < 0) + return -1; + sp->audio_src.freq = frame->sample_rate; + sp->audio_src.fmt = frame->format; + } + + int resampled_data_size; + if (sp->swr_ctx) { + int out_count = (int)((int64_t)frame->nb_samples * MRSampleRate / frame->sample_rate + 256); + int out_size = av_samples_get_buffer_size(NULL, MRNBChannels, out_count, MRSampleFormat, 0); + int len2; + if (out_size < 0) { + av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size() failed\n"); + return -1; + } + av_fast_malloc(&sp->audio_buf1, &sp->audio_buf1_size, out_size); + + const uint8_t **in = (const uint8_t **)frame->extended_data; + uint8_t **out = &sp->audio_buf1; + + if (!sp->audio_buf1) + return AVERROR(ENOMEM); + len2 = swr_convert(sp->swr_ctx, out, out_count, in, frame->nb_samples); + if (len2 < 0) { + av_log(NULL, AV_LOG_ERROR, "swr_convert() failed\n"); + return -1; + } + if (len2 == out_count) { + av_log(NULL, AV_LOG_WARNING, "audio buffer is probably too small\n"); + if (swr_init(sp->swr_ctx) < 0) + swr_free(&sp->swr_ctx); + } + sp->audio_buf = sp->audio_buf1; + int bytes_per_sample = av_get_bytes_per_sample(MRSampleFormat); + resampled_data_size = len2 * MRNBChannels * bytes_per_sample; + } else { + sp->audio_buf = frame->data[0]; + resampled_data_size = av_samples_get_buffer_size(NULL, + frame->ch_layout.nb_channels, + frame->nb_samples, + frame->format, + 1); + } + + /* update the audio clock with the pts */ + if (!isnan(af->pts)) + sp->audio_clock = af->pts; + + sp->audio_clock_serial = af->serial; + +// if (file_pcm_l == NULL) { +// file_pcm_l = fopen("/Users/matt/Library/Containers/2E018519-4C6C-4E16-B3B1-9F3ED37E67E5/Data/tmp/3.pcm", "wb+"); +// } +// fwrite(sp->audio_buf, resampled_data_size, 1, file_pcm_l); + + frame_queue_next(&sp->frameq); + return resampled_data_size; +} + +static int bytes_per_millisecond(void) +{ + static int _bytes_per_millisecond = 0; + + if (_bytes_per_millisecond == 0) { + _bytes_per_millisecond = av_samples_get_buffer_size(NULL, MRNBChannels, MRSampleRate / 1000, MRSampleFormat, 1); + } + return _bytes_per_millisecond; +} + +static int bytes_per_sec(void) +{ + return bytes_per_millisecond() * 1000; +} + +int mr_stream_peek_get_data(MRStreamPeeker *peeker, unsigned char *buffer, int len, double * pts_begin, double * pts_end) +{ + const int len_want = len; + double begin = -1, end = -1; + + if (!peeker) { + return -1; + } + + while (len > 0) { + if (peeker->audio_buf_index >= peeker->audio_buf_size) { + int audio_size = audio_decode_frame(peeker); + if (audio_size < 0) { + /* if error, just output silence */ + peeker->audio_buf = NULL; + peeker->audio_buf_size = 0; + goto the_end; + } else { + peeker->audio_buf_size = audio_size; + } + peeker->audio_buf_index = 0; + } + + if (streamComponent_get_pkt_serial(peeker->opaque) != peeker->pktq.serial) { + peeker->audio_buf_index = peeker->audio_buf_size; + break; + } + int rest_len = peeker->audio_buf_size - peeker->audio_buf_index; + + if (begin < 0) { + begin = peeker->audio_clock + peeker->audio_buf_index / bytes_per_sec(); + } + + if (rest_len > len) + rest_len = len; + memcpy(buffer, (uint8_t *)peeker->audio_buf + peeker->audio_buf_index, rest_len); + len -= rest_len; + buffer += rest_len; + peeker->audio_buf_index += rest_len; + } +the_end: + + end = peeker->audio_clock + peeker->audio_buf_index / bytes_per_sec(); + if (pts_begin) { + *pts_begin = begin; + } + + if (pts_end) { + *pts_end = end; + } + + return len_want - len; +} + +int mr_stream_peek_open_filepath(MRStreamPeeker *peeker, const char *file_name, int idx) +{ + if (!peeker) { + return -1; + } + + if (!file_name || strlen(file_name) == 0) { + return -2; + } + + int ret = 0; + AVFormatContext* ic = NULL; + AVCodecContext* avctx = NULL; + + if (avformat_open_input(&ic, file_name, NULL, NULL) < 0) { + ret = -1; + goto fail; + } + + if (avformat_find_stream_info(ic, NULL) < 0) { + ret = -2; + goto fail; + } + + if (ic) { + av_log(NULL, AV_LOG_DEBUG, "ex subtitle demuxer:%s\n",ic->iformat->name); + } + + if (idx == -1) { + int st_index_video = -1; + int st_index_audio = -1; + for (int i = 0; i < ic->nb_streams; i++) { + AVStream *st = ic->streams[i]; + enum AVMediaType type = st->codecpar->codec_type; + st->discard = AVDISCARD_ALL; + // choose first h264 + + if (type == AVMEDIA_TYPE_VIDEO) { + enum AVCodecID codec_id = st->codecpar->codec_id; + if (codec_id == AV_CODEC_ID_H264) { + st_index_video = i; + break; + } + } + } + + st_index_video = + av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, + st_index_video, -1, NULL, 0); + + st_index_audio = + av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, + st_index_audio, + st_index_video, + NULL, 0); + if (st_index_audio >= 0) { + idx = st_index_audio; + } + } + + if (idx == -1) { + av_log(NULL, AV_LOG_WARNING, "could find audio stream:%s\n", file_name); + ret = -3; + goto fail; + } + + AVStream *stream = ic->streams[idx]; + stream->discard = AVDISCARD_DEFAULT; + + if (!stream) { + ret = -3; + av_log(NULL, AV_LOG_ERROR, "none audio stream in %s\n", file_name); + goto fail; + } + + const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id); + if (!codec) { + av_log(NULL, AV_LOG_WARNING, "could find codec:%s for %s\n", + file_name, avcodec_get_name(stream->codecpar->codec_id)); + ret = -4; + goto fail; + } + + avctx = avcodec_alloc_context3(NULL); + if (!avctx) { + ret = -5; + goto fail; + } + + if (avcodec_parameters_to_context(avctx, stream->codecpar) < 0) { + ret = -6; + goto fail; + } + //so important,ohterwise, sub frame has not pts. + avctx->pkt_timebase = stream->time_base; + + if (avcodec_open2(avctx, codec, NULL) < 0) { + ret = -7; + goto fail; + } + + if (streamComponent_open(&peeker->opaque, idx, ic, avctx, &peeker->pktq, &peeker->frameq) != 0) { + ret = -8; + goto fail; + } + peeker->duration = (int)(ic->duration / AV_TIME_BASE); + peeker->ic = ic; + peeker->stream_idx = idx; + return 0; +fail: + if (ret < 0) { + if (ic) + avformat_close_input(&ic); + if (avctx) + avcodec_free_context(&avctx); + } + return ret; +} + +int mr_stream_peek_close(MRStreamPeeker *peeker) +{ + if(!peeker) { + return -1; + } + + MRStreamComponent *opaque = peeker->opaque; + + if(!opaque) { + if (peeker->ic) + avformat_close_input(&peeker->ic); + return -2; + } + + int r = streamComponent_close(&opaque); + SDL_LockMutex(peeker->mutex); + peeker->opaque = NULL; + if (peeker->ic) + avformat_close_input(&peeker->ic); + SDL_UnlockMutex(peeker->mutex); + return r; +} + +void mr_stream_peek_destroy(MRStreamPeeker **peeker_out) +{ + if (!peeker_out) { + return; + } + + MRStreamPeeker *peeker = *peeker_out; + if (!peeker) { + return; + } + + mr_stream_peek_close(peeker); + + SDL_DestroyMutex(peeker->mutex); + + av_freep(peeker_out); +} + +int mr_stream_peek_get_buffer_size(int millisecond) +{ + return bytes_per_millisecond() * millisecond; +} + +int mr_stream_duration(MRStreamPeeker *peeker) +{ + if (peeker) { + return peeker->duration; + } + return 0; +} diff --git a/ijkmedia/tools/mr_stream_peek.h b/ijkmedia/tools/mr_stream_peek.h new file mode 100644 index 0000000000..a828c2943d --- /dev/null +++ b/ijkmedia/tools/mr_stream_peek.h @@ -0,0 +1,26 @@ +// +// mr_stream_peek.h +// +// ijkplayer not use the file, but the file will be used by other module in app. +// +// Created by Reach Matt on 2023/9/7. +// + +#ifndef mr_stream_peek_h +#define mr_stream_peek_h + +#include +typedef struct MRStreamPeeker MRStreamPeeker; + +int mr_stream_peek_create(MRStreamPeeker **peeker_out,int frameCacheCount); +int mr_stream_peek_open_filepath(MRStreamPeeker *peeker, const char *file_name, int idx); + +int mr_stream_peek_get_opened_stream_idx(MRStreamPeeker *peeker); +int mr_stream_peek_seek_to(MRStreamPeeker *peeker, float sec); +int mr_stream_peek_get_data(MRStreamPeeker *peeker, unsigned char *buffer, int len, double * pts_begin, double * pts_end); +int mr_stream_peek_close(MRStreamPeeker *peeker); +void mr_stream_peek_destroy(MRStreamPeeker **peeker_out); +int mr_stream_peek_get_buffer_size(int millisecond); +int mr_stream_duration(MRStreamPeeker *peeker); + +#endif /* mr_stream_peek_h */ diff --git a/ijkmedia/wrapper/apple/IJKFFMonitor.h b/ijkmedia/wrapper/apple/IJKFFMonitor.h index eb2058f0f1..651aa47932 100644 --- a/ijkmedia/wrapper/apple/IJKFFMonitor.h +++ b/ijkmedia/wrapper/apple/IJKFFMonitor.h @@ -29,6 +29,7 @@ @property(nonatomic) NSDictionary *videoMeta; @property(nonatomic) NSDictionary *audioMeta; @property(nonatomic) NSDictionary *subtitleMeta; +@property(nonatomic) NSArray *chapterMetaArr; @property(nonatomic, readonly) int64_t duration; // milliseconds @property(nonatomic, readonly) int64_t bitrate; // bit / sec diff --git a/ijkmedia/wrapper/apple/IJKFFMoviePlayerController.h b/ijkmedia/wrapper/apple/IJKFFMoviePlayerController.h index af8a44bb0c..5b20291b2f 100644 --- a/ijkmedia/wrapper/apple/IJKFFMoviePlayerController.h +++ b/ijkmedia/wrapper/apple/IJKFFMoviePlayerController.h @@ -27,10 +27,18 @@ #import "IJKVideoRenderingProtocol.h" // media meta -#define k_IJKM_KEY_FORMAT @"format" -#define k_IJKM_KEY_DURATION_US @"duration_us" -#define k_IJKM_KEY_START_US @"start_us" -#define k_IJKM_KEY_BITRATE @"bitrate" +#define k_IJKM_KEY_FORMAT @"format" +#define k_IJKM_KEY_DURATION_US @"duration_us" +#define k_IJKM_KEY_START_US @"start_us" +#define k_IJKM_KEY_BITRATE @"bitrate" +#define k_IJKM_KEY_ENCODER @"encoder" +#define k_IJKM_KEY_MINOR_VER @"minor_version" +#define k_IJKM_KEY_COMPATIBLE_BRANDS @"compatible_brands" +#define k_IJKM_KEY_MAJOR_BRAND @"major_brand" +#define k_IJKM_KEY_ARTIST @"artist" +#define k_IJKM_KEY_ALBUM @"album" +#define k_IJKM_KEY_TYER @"TYER" + // stream meta #define k_IJKM_KEY_TYPE @"type" @@ -55,14 +63,15 @@ #define k_IJKM_KEY_SAR_DEN @"sar_den" // stream: audio #define k_IJKM_KEY_SAMPLE_RATE @"sample_rate" -//#define k_IJKM_KEY_CHANNEL_LAYOUT @"channel_layout" -#define k_IJKM_KEY_ARTIST @"artist" -#define k_IJKM_KEY_ALBUM @"album" -#define k_IJKM_KEY_TYER @"TYER" +//audio meta also has "title" and "language" key +//#define k_IJKM_KEY_TITLE @"title" +//#define k_IJKM_KEY_LANGUAGE @"language" + // stream: subtitle #define k_IJKM_KEY_TITLE @"title" #define k_IJKM_KEY_LANGUAGE @"language" #define k_IJKM_KEY_EX_SUBTITLE_URL @"ex_subtile_url" + #define kk_IJKM_KEY_STREAMS @"streams" typedef enum IJKLogLevel { diff --git a/ijkmedia/wrapper/apple/IJKFFMoviePlayerController.m b/ijkmedia/wrapper/apple/IJKFFMoviePlayerController.m index 9368836563..89cea2b447 100644 --- a/ijkmedia/wrapper/apple/IJKFFMoviePlayerController.m +++ b/ijkmedia/wrapper/apple/IJKFFMoviePlayerController.m @@ -22,7 +22,6 @@ */ #import "IJKFFMoviePlayerController.h" -#import "IJKSDLGLView.h" #import "IJKMetalView.h" #import "IJKSDLHudControl.h" #import "IJKFFMoviePlayerDef.h" @@ -35,6 +34,8 @@ #include "string.h" #if TARGET_OS_IOS #import "IJKAudioKit.h" +#else +#import "IJKSDLGLView.h" #endif #include "../ijkmedia/ijkplayer/apple/ijkplayer_ios.h" @@ -114,6 +115,7 @@ @implementation IJKFFMoviePlayerController { @synthesize isSeekBuffering = _isSeekBuffering; @synthesize isAudioSync = _isAudioSync; @synthesize isVideoSync = _isVideoSync; +@synthesize subtitlePreference = _subtitlePreference; #define FFP_IO_STAT_STEP (50 * 1024) @@ -212,16 +214,18 @@ - (void)_initWithContent:(NSURL *)aUrl options:(IJKFFOptions *)options glView:(U ijkmp_set_option(_mediaPlayer, IJKMP_OPT_CATEGORY_PLAYER, "overlay-format", "fcc-_es2"); //ijkmp_set_option(_mediaPlayer,IJKMP_OPT_CATEGORY_FORMAT,"safe", 0); //ijkmp_set_option(_mediaPlayer,IJKMP_OPT_CATEGORY_PLAYER,"protocol_whitelist","ffconcat,file,http,https"); + //httpproxy ijkmp_set_option(_mediaPlayer,IJKMP_OPT_CATEGORY_FORMAT,"protocol_whitelist","ijkio,ijkhttphook,concat,http,tcp,https,tls,file,bluray,dvd,rtmp,rtsp,rtp,srtp,udp"); + _subtitlePreference = ijk_subtitle_default_preference(); // init hud _hudCtrl = [IJKSDLHudControl new]; self.shouldShowHudView = options.showHudView; - + [options applyTo:_mediaPlayer]; + _pauseInBackground = NO; - // init extra _keepScreenOnWhilePlaying = YES; [self setScreenOn:YES]; @@ -245,19 +249,21 @@ - (id)initWithContentURL:(NSURL *)aUrl withOptions:(IJKFFOptions *)options UIView *glView = nil; #if TARGET_OS_IOS CGRect rect = [[UIScreen mainScreen] bounds]; + rect.origin = CGPointZero; + glView = [[IJKMetalView alloc] initWithFrame:rect]; #else CGRect rect = [[[NSScreen screens] firstObject]frame]; rect.origin = CGPointZero; - #endif if (!options.metalRenderer) { glView = [[IJKSDLGLView alloc] initWithFrame:rect]; } else { - if (@available(macOS 10.13, ios 11.0, *)) { + if (@available(macOS 10.13, *)) { glView = [[IJKMetalView alloc] initWithFrame:rect]; } else { glView = [[IJKSDLGLView alloc] initWithFrame:rect]; } } + #endif [self _initWithContent:aUrl options:options glView:glView]; } return self; @@ -307,21 +313,23 @@ - (void)prepareToPlay NSString *render = [self.view name]; [self setHudValue:render forKey:@"v-renderer"]; -// if (![_contentURL isFileURL]) { -// [self setHudValue:nil forKey:@"scheme"]; -// [self setHudValue:nil forKey:@"host"]; -// [self setHudValue:nil forKey:@"path"]; -// [self setHudValue:nil forKey:@"ip"]; -// [self setHudValue:nil forKey:@"tcp-info"]; -// [self setHudValue:nil forKey:@"http"]; -// [self setHudValue:nil forKey:@"tcp-spd"]; -// [self setHudValue:nil forKey:@"t-prepared"]; -// [self setHudValue:nil forKey:@"t-render"]; -// [self setHudValue:nil forKey:@"t-preroll"]; -// [self setHudValue:nil forKey:@"t-http-open"]; -// [self setHudValue:nil forKey:@"t-http-seek"]; -// } -// + if (![_contentURL isFileURL]) { + [self setHudValue:nil forKey:@"scheme"]; + [self setHudValue:nil forKey:@"host"]; + [self setHudValue:nil forKey:@"path"]; + [self setHudValue:nil forKey:@"ip"]; + [self setHudValue:nil forKey:@"tcp-info"]; + [self setHudValue:nil forKey:@"http"]; + [self setHudValue:nil forKey:@"tcp-spd"]; + [self setHudValue:nil forKey:@"t-prepared"]; + [self setHudValue:nil forKey:@"t-render"]; + [self setHudValue:nil forKey:@"t-preroll"]; + [self setHudValue:nil forKey:@"t-http-open"]; + [self setHudValue:nil forKey:@"t-http-seek"]; + } else { + [self setHudValue:nil forKey:@"path"]; + } + [self setHudUrl:_contentURL]; //解决中文路径 bluray://中文编码/打不开流问题 @@ -380,14 +388,15 @@ - (BOOL)loadSubtitlesOnly:(NSArray *)urlArr if (!_mediaPlayer || urlArr.count == 0) return NO; const char *files[512] = {0}; - for (int i = 0; i < MIN(urlArr.count, 512); i++) { + NSUInteger maxCount = MIN(urlArr.count, 512); + for (int i = 0; i < maxCount; i++) { NSURL *url = [urlArr objectAtIndex:i]; NSString *filePath = [url isFileURL] ? [url path] : [url absoluteString]; const char *file = [filePath UTF8String]; files[i] = file; } - int ret = ijkmp_addOnly_external_subtitles(_mediaPlayer, files, (int)urlArr.count); + int ret = ijkmp_addOnly_external_subtitles(_mediaPlayer, files, (int)maxCount); return ret > 0; } @@ -652,9 +661,6 @@ - (void)shutdown #endif [self setScreenOn:NO]; [self destroyHud]; - //release glview in main thread. - _view = _glView = nil; - ijkmp_ios_set_glview(_mediaPlayer, nil); [self performSelectorInBackground:@selector(shutdownWaitStop:) withObject:self]; } @@ -816,9 +822,6 @@ - (void)changeNaturalSize postNotificationName:IJKMPMovieNaturalSizeAvailableNotification object:self userInfo:@{@"size":NSStringFromSize(self->_naturalSize)}]; #endif - if ([self.view respondsToSelector:@selector(videoNaturalSizeChanged:)]) { - [self.view videoNaturalSizeChanged:self->_naturalSize]; - } } } @@ -1005,10 +1008,10 @@ - (void)refreshHudView formatedDurationMilli(_monitor.lastHttpOpenDuration), _monitor.httpOpenCount] forKey:@"t-http-open"]; -// [self setHudValue:[NSString stringWithFormat:@"%@ / %d", -// formatedDurationMilli(_monitor.lastHttpSeekDuration), -// _monitor.httpSeekCount] -// forKey:@"t-http-seek"]; + [self setHudValue:[NSString stringWithFormat:@"%@ / %d", + formatedDurationMilli(_monitor.lastHttpSeekDuration), + _monitor.httpSeekCount] + forKey:@"t-http-seek"]; } } @@ -1028,8 +1031,12 @@ - (void)startHudTimer hudView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin; CGFloat screenWidth = [[UIScreen mainScreen]bounds].size.width; #else - hudView.autoresizingMask = NSViewHeightSizable | NSViewMinXMargin; - CGFloat screenWidth = [[[NSScreen screens] firstObject]frame].size.width; + hudView.autoresizingMask = NSViewHeightSizable | NSViewMinXMargin | NSViewMinYMargin | NSViewMaxYMargin; + NSScreen *screen = self.view.window.screen; + if (!screen) { + screen = [[NSScreen screens] firstObject]; + } + CGFloat screenWidth = [screen frame].size.width; #endif rect.size.width = MIN(screenWidth / 3.0, 350); rect.origin.x = CGRectGetWidth(self.view.bounds) - rect.size.width; @@ -1108,13 +1115,13 @@ - (void)enableAccurateSeek:(BOOL)open _enableAccurateSeek = open ? 1 : 2; } else { _enableAccurateSeek = 0; - ijk_set_enable_accurate_seek(_mediaPlayer, open); + ijkmp_set_enable_accurate_seek(_mediaPlayer, open); } } - (void)stepToNextFrame { - ijk_step_to_next_frame(_mediaPlayer); + ijkmp_step_to_next_frame(_mediaPlayer); } - (BOOL)shouldShowHudView @@ -1222,10 +1229,13 @@ - (void) traverseIJKMetaData:(IjkMediaMeta*)rawMeta fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_DURATION_US, nil); fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_START_US, nil); fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_BITRATE, nil); - fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_ARTIST, nil); fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_ALBUM, nil); fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_TYER, nil); + fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_ENCODER, nil); + fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_MINOR_VER, nil); + fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_COMPATIBLE_BRANDS, nil); + fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_MAJOR_BRAND, nil); fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_VIDEO_STREAM, nil); fillMetaInternal(newMediaMeta, rawMeta, IJKM_KEY_AUDIO_STREAM, nil); @@ -1294,6 +1304,20 @@ - (void) traverseIJKMetaData:(IjkMediaMeta*)rawMeta if (subtitle_stream == i) { _monitor.subtitleMeta = streamMeta; } + } else if (0 == strcmp(type, IJKM_VAL_TYPE__CHAPTER)) { + NSMutableArray *chapterMetaArr = [NSMutableArray array]; + size_t count = ijkmeta_get_children_count_l(streamRawMeta); + for (size_t i = 0; i < count; ++i) { + IjkMediaMeta *chapterRawMeta = ijkmeta_get_child_l(streamRawMeta, i); + NSMutableDictionary *chapterMeta = [[NSMutableDictionary alloc] init]; + fillMetaInternal(chapterMeta, chapterRawMeta, IJKM_META_KEY_ID, nil); + fillMetaInternal(chapterMeta, chapterRawMeta, IJKM_META_KEY_START, nil); + fillMetaInternal(chapterMeta, chapterRawMeta, IJKM_META_KEY_END, nil); + //fill title meta only,expand other later + fillMetaInternal(chapterMeta, chapterRawMeta, IJKM_META_KEY_TITLE, nil); + [chapterMetaArr addObject:chapterMeta]; + } + _monitor.chapterMetaArr = chapterMetaArr.count > 0 ? chapterMetaArr : nil; } } } @@ -1357,17 +1381,13 @@ - (void)postEvent: (IJKFFMoviePlayerMessage *)msg case FFP_MSG_SELECTED_STREAM_CHANGED: {//stream changed msg IjkMediaMeta *rawMeta = ijkmp_get_meta_l(_mediaPlayer); [self traverseIJKMetaData:rawMeta]; - //clean old subtitle - if (!self.monitor.subtitleMeta) { - if ([self.view respondsToSelector:@selector(cleanSubtitle)]) { - if (![self isPlaying]) { - [self.view cleanSubtitle]; - } - } - } [[NSNotificationCenter defaultCenter] postNotificationName:IJKMPMoviePlayerSelectedStreamDidChangeNotification object:self]; break; } + case FFP_MSG_SELECTING_STREAM_FAILED: {//select stream failed + [[NSNotificationCenter defaultCenter] postNotificationName:IJKMoviePlayerSelectingStreamDidFailed object:self userInfo:@{IJKMoviePlayerSelectingStreamIDUserInfoKey : @(avmsg->arg1), IJKMoviePlayerSelectingStreamErrUserInfoKey : @(avmsg->arg2)}]; + break; + } case FFP_MSG_PREPARED: { _monitor.prepareDuration = (int64_t)SDL_GetTickHR() - _monitor.prepareStartTick; //prepared not send,beacuse FFP_MSG_VIDEO_DECODER_OPEN event already send @@ -1559,10 +1579,6 @@ - (void)postEvent: (IJKFFMoviePlayerMessage *)msg if (_videoZRotateDegrees != avmsg->arg1) { _videoZRotateDegrees = avmsg->arg1; - if ([self.view respondsToSelector:@selector(videoZRotateDegrees:)]) { - [self.view videoZRotateDegrees:_videoZRotateDegrees]; - } - [[NSNotificationCenter defaultCenter] postNotificationName:IJKMPMovieZRotateAvailableNotification object:self userInfo:@{@"degrees":@(_videoZRotateDegrees)}]; @@ -1578,7 +1594,7 @@ - (void)postEvent: (IJKFFMoviePlayerMessage *)msg case FFP_MSG_AFTER_SEEK_FIRST_FRAME: { int du = avmsg->arg1; if (_enableAccurateSeek > 0) { - ijk_set_enable_accurate_seek(_mediaPlayer, _enableAccurateSeek == 1); + ijkmp_set_enable_accurate_seek(_mediaPlayer, _enableAccurateSeek == 1); _enableAccurateSeek = 0; } [[NSNotificationCenter defaultCenter] @@ -1640,8 +1656,10 @@ static int media_player_msg_loop(void* arg) - (void)setHudUrl:(NSURL *)url { if ([[NSThread currentThread] isMainThread]) { - [self setHudValue:url.scheme forKey:@"scheme"]; - [self setHudValue:url.host forKey:@"host"]; + if (![url.scheme isEqualToString:@"file"]) { + [self setHudValue:url.scheme forKey:@"scheme"]; + [self setHudValue:url.host forKey:@"host"]; + } [self setHudValue:url.path forKey:@"path"]; } else { dispatch_async(dispatch_get_main_queue(), ^{ @@ -1708,8 +1726,10 @@ static int onInjectTcpIOControl(IJKFFMoviePlayerController *mpc, idfd] forKey:@"tcp-info"]; return 0; + } NSString *urlString = [NSString stringWithUTF8String:realData->ip]; @@ -2087,7 +2107,7 @@ - (void)exchangeSelectedStream:(int)streamIdx long pst = ijkmp_get_current_position(_mediaPlayer); int r = ijkmp_set_stream_selected(_mediaPlayer,streamIdx,1); if (r > 0) { - ijkmp_seek_to(_mediaPlayer, pst); + ijkmp_seek_to(_mediaPlayer, pst-200); } } } @@ -2113,4 +2133,12 @@ - (float)currentSubtitleExtraDelay return ijkmp_get_subtitle_extra_delay(_mediaPlayer); } +- (void)setSubtitlePreference:(IJKSDLSubtitlePreference)subtitlePreference +{ + if (!isIJKSDLSubtitlePreferenceEqual(&_subtitlePreference, &subtitlePreference)) { + _subtitlePreference = subtitlePreference; + ijkmp_set_subtitle_preference(_mediaPlayer, &subtitlePreference); + } +} + @end diff --git a/ijkmedia/wrapper/apple/IJKInternalRenderView.h b/ijkmedia/wrapper/apple/IJKInternalRenderView.h index b34ef8580c..3ba7eb1c5a 100644 --- a/ijkmedia/wrapper/apple/IJKInternalRenderView.h +++ b/ijkmedia/wrapper/apple/IJKInternalRenderView.h @@ -13,7 +13,9 @@ NS_ASSUME_NONNULL_BEGIN @interface IJKInternalRenderView : NSObject +#if TARGET_OS_OSX + (UIView *)createGLRenderView; +#endif + (UIView *)createMetalRenderView NS_AVAILABLE(10_13, 11_0); diff --git a/ijkmedia/wrapper/apple/IJKInternalRenderView.m b/ijkmedia/wrapper/apple/IJKInternalRenderView.m index 7d7f72db3b..d230babc0b 100644 --- a/ijkmedia/wrapper/apple/IJKInternalRenderView.m +++ b/ijkmedia/wrapper/apple/IJKInternalRenderView.m @@ -6,15 +6,19 @@ // #import "IJKInternalRenderView.h" +#if TARGET_OS_OSX #import "IJKSDLGLView.h" +#endif #import "IJKMetalView.h" @implementation IJKInternalRenderView +#if TARGET_OS_OSX + (UIView *)createGLRenderView { return [[IJKSDLGLView alloc] init]; } +#endif + (UIView *)createMetalRenderView { diff --git a/ijkmedia/wrapper/apple/IJKMediaPlayback.h b/ijkmedia/wrapper/apple/IJKMediaPlayback.h index 399d2758bf..35b72fc792 100644 --- a/ijkmedia/wrapper/apple/IJKMediaPlayback.h +++ b/ijkmedia/wrapper/apple/IJKMediaPlayback.h @@ -70,6 +70,7 @@ typedef NS_ENUM(NSInteger, IJKMPMovieTimeOption) { - (void)prepareToPlay; - (BOOL)loadThenActiveSubtitle:(NSURL*)url; - (BOOL)loadSubtitleOnly:(NSURL*)url; +//最多512个字幕 - (BOOL)loadSubtitlesOnly:(NSArray*)urlArr; - (void)play; - (void)pause; @@ -110,6 +111,9 @@ typedef NS_ENUM(NSInteger, IJKMPMovieTimeOption) { - (UIImage *)thumbnailImageAtCurrentTime; #endif +//subtitle preference +@property(nonatomic) IJKSDLSubtitlePreference subtitlePreference; + #pragma mark Notifications #ifdef __cplusplus @@ -183,7 +187,12 @@ IJK_EXTERN NSString *const IJKMPMoviePlayerPlaybackRecvWarningNotification; /*wa IJK_EXTERN NSString *const IJKMPMoviePlayerPlaybackWarningReasonUserInfoKey; /*useinfo's key,value is int.*/ //user info's state key:1 means begin,2 means end. IJK_EXTERN NSString *const IJKMoviePlayerHDRAnimationStateChanged; - +//select stream failed user info reason key +IJK_EXTERN NSString *const IJKMoviePlayerSelectingStreamIDUserInfoKey; +//select stream failed err code key +IJK_EXTERN NSString *const IJKMoviePlayerSelectingStreamErrUserInfoKey; +//select stream failed. +IJK_EXTERN NSString *const IJKMoviePlayerSelectingStreamDidFailed; @end #pragma mark IJKMediaUrlOpenDelegate diff --git a/ijkmedia/wrapper/apple/IJKMediaPlayback.m b/ijkmedia/wrapper/apple/IJKMediaPlayback.m index 696bc60dc2..91bea29136 100644 --- a/ijkmedia/wrapper/apple/IJKMediaPlayback.m +++ b/ijkmedia/wrapper/apple/IJKMediaPlayback.m @@ -69,6 +69,12 @@ NSString *const IJKMoviePlayerHDRAnimationStateChanged = @"IJKMoviePlayerHDRAnimationStateChanged"; +NSString *const IJKMoviePlayerSelectingStreamIDUserInfoKey = + @"stream-id"; +NSString *const IJKMoviePlayerSelectingStreamErrUserInfoKey = + @"err-code"; +NSString *const IJKMoviePlayerSelectingStreamDidFailed = @"IJKMoviePlayerSelectingStreamDidFailed"; + @implementation IJKMediaUrlOpenData { NSString *_url; BOOL _handled; diff --git a/ijkmedia/wrapper/apple/IJKVideoRenderingProtocol.h b/ijkmedia/wrapper/apple/IJKVideoRenderingProtocol.h index ae738aeba7..086c5730b3 100644 --- a/ijkmedia/wrapper/apple/IJKVideoRenderingProtocol.h +++ b/ijkmedia/wrapper/apple/IJKVideoRenderingProtocol.h @@ -34,7 +34,7 @@ typedef NSView UIView; #else #import #endif - +#import "ff_subtitle_def.h" typedef NS_ENUM(NSInteger, IJKMPMovieScalingMode) { IJKMPMovieScalingModeAspectFit, // Uniform scale until one dimension fits @@ -42,16 +42,7 @@ typedef NS_ENUM(NSInteger, IJKMPMovieScalingMode) { IJKMPMovieScalingModeFill // Non-uniform scale. Both render dimensions will exactly match the visible bounds }; -@interface IJKSDLSubtitle : NSObject - -@property(nonatomic, copy) NSString * text; -//bitmap -@property(nonatomic) int w; -@property(nonatomic) int h; -@property(nonatomic) uint8_t *pixels; //pixels with length w * h, in BGRA pixel format - -@end - +typedef struct SDL_TextureOverlay SDL_TextureOverlay; @interface IJKOverlayAttach : NSObject //video frame normal size not alignmetn,maybe not equal to currentVideoPic's size. @@ -69,17 +60,42 @@ typedef NS_ENUM(NSInteger, IJKMPMovieScalingMode) { //degrees @property(nonatomic) int autoZRotate; @property(nonatomic) CVPixelBufferRef videoPicture; -@property(nonatomic) IJKSDLSubtitle *sub; @property(nonatomic) NSArray *videoTextures; + +@property(nonatomic) SDL_TextureOverlay *overlay; @property(nonatomic) id subTexture; + @end -typedef struct _IJKSDLSubtitlePreference IJKSDLSubtitlePreference; -struct _IJKSDLSubtitlePreference { - float ratio;//scale - int32_t color; - float bottomMargin;//[0.0,1.0] -}; +static inline uint32_t color2int(UIColor *color) { +#if TARGET_OS_OSX + if (@available(macOS 10.13, *)) { + if (color.type != NSColorSpaceModelRGB) { + + } + } + if (![color.colorSpaceName isEqualToString:NSDeviceRGBColorSpace] && ![color.colorSpaceName isEqualToString:NSCalibratedRGBColorSpace]) { + color = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + } +#endif + CGFloat r,g,b,a; + [color getRed:&r green:&g blue:&b alpha:&a]; + + r *= 255; + g *= 255; + b *= 255; + a *= 255; + return (uint32_t)a + ((uint32_t)b << 8) + ((uint32_t)g << 16) + ((uint32_t)r << 24); +} + +static inline UIColor * int2color(uint32_t abgr) { + CGFloat r,g,b,a; + a = ((float)(abgr & 0xFF)) / 255.0; + b = ((float)((abgr & 0xFF00) >> 8)) / 255.0; + g = (float)(((abgr & 0xFF0000) >> 16)) / 255.0; + r = ((float)((abgr & 0xFF000000) >> 24)) / 255.0; + return [UIColor colorWithRed:r green:g blue:b alpha:a]; +} typedef enum _IJKSDLRotateType { IJKSDLRotateNone, @@ -124,11 +140,9 @@ typedef enum : NSUInteger { if you update these preference blow, when player paused, you can call -[setNeedsRefreshCurrentPic] method let current picture refresh right now. */ -// subtitle preference -@property(nonatomic) IJKSDLSubtitlePreference subtitlePreference; // rotate preference @property(nonatomic) IJKSDLRotatePreference rotatePreference; -// color conversion perference +// color conversion preference @property(nonatomic) IJKSDLColorConversionPreference colorPreference; // user defined display aspect ratio @property(nonatomic) IJKSDLDARPreference darPreference; @@ -148,14 +162,10 @@ typedef enum : NSUInteger { - (CGImageRef)snapshot:(IJKSDLSnapshotType)aType; #endif - (NSString *)name; +- (id)context; + @optional; -//when video size changed will call videoNaturalSizeChanged. -- (void)videoNaturalSizeChanged:(CGSize)size; -//when video z rotate degrees changed will call videoZRotateDegrees. -- (void)videoZRotateDegrees:(NSInteger)degrees; - (void)setBackgroundColor:(uint8_t)r g:(uint8_t)g b:(uint8_t)b; -//when palyer paused,close subtile stream will call this method. -- (void)cleanSubtitle; @end diff --git a/shell/README.md b/shell/README.md index 5888d468f0..9e3bbeb50a 100644 --- a/shell/README.md +++ b/shell/README.md @@ -2,9 +2,9 @@ **What's MRFFToolChain?** -MRFFToolChain products was built for my FFmepg tutorial : [https://github.com/debugly/FFmpegTutorial](https://github.com/debugly/FFmpegTutorial). +MRFFToolChain products was built for ijkplayer : [https://github.com/debugly/ijkplayer](https://github.com/debugly/ijkplayer). -At present MRFFToolChain contained OpenSSL、FFmpeg 、libyuv、libopus、libbluray、etc. +At present MRFFToolChain contained `ass、bluray、dav1d、dvdread、ffmpeg、freetype、fribidi、harfbuzz、libyuv、openssl、opus、unibreak`. ## Folder structure @@ -30,12 +30,18 @@ At present MRFFToolChain contained OpenSSL、FFmpeg 、libyuv、libopus、libblu │   └── module.sh -> module-full.sh ├── init-any.sh #初始化源码仓库 ├── init-cfgs #三方库的配置,包括库名,git仓库地址等信息 +│   ├── ass │   ├── bluray │   ├── dav1d +│   ├── dvdread │   ├── ffmpeg +│   ├── freetype +│   ├── fribidi +│   ├── harfbuzz │   ├── libyuv │   ├── openssl -│   └── opus +│   ├── opus +│   └── unibreak ├── install-pre-any.sh #直接从github下载预编译好的lib ├── ios #ios 平台编译脚本 │   └── compile-any.sh @@ -43,18 +49,17 @@ At present MRFFToolChain contained OpenSSL、FFmpeg 、libyuv、libopus、libblu │   └── compile-any.sh ├── patches #补丁 │   ├── README.md -│   ├── dav1d │   ├── ffmpeg -> ffmpeg-release-5.1 │   ├── ffmpeg-n4.0 │   └── ffmpeg-release-5.1 ├── tools #基础脚本 -│   ├── copy-local-repo.sh -│   ├── env_assert.sh -│   ├── init-repo.sh -│   ├── install-pre-lib.sh -│   ├── pull-repo-base.sh -│   └── pull-repo-ref.sh -└── version.sh + ├── copy-local-repo.sh + ├── env_assert.sh + ├── init-repo.sh + ├── install-pre-lib.sh + ├── pull-repo-base.sh + ├── pull-repo-ref.sh + └── sync-lastest-private.sh ``` ## Download Pre-compiled libs @@ -144,14 +149,20 @@ At present MRFFToolChain contained OpenSSL、FFmpeg 、libyuv、libopus、libblu 如果 github 上的仓库克隆较慢,或者需要使用内网私有仓库,可在执行编译脚本前声明对应的环境变量! -| 名称 | 默认仓库 | 使用镜像 | -| --------- | ------------------------------------------------ | -------------------------------------------------- | -| FFmpeg | https://github.com/bilibili/FFmpeg.git | export GIT_FFMPEG_UPSTREAM=git@xx:yy/ffmpeg.git | -| libYUV | https://github.com/lemenkov/libyuv.git | export GIT_LIBYUV_UPSTREAM=git@xx:yy/libyuv.git | -| OpenSSL | https://github.com/openssl/openssl.git | export GIT_OPENSSL_UPSTREAM=git@xx:yy/openssl.git | -| Opus | https://gitlab.xiph.org/xiph/opus.git | export GIT_OPUS_UPSTREAM=git@xx:yy/opusfile.git | -| libbluray | https://code.videolan.org/videolan/libbluray.git | export GIT_BLURAY_UPSTREAM=git@xx:yy/libbluray.git | -| dav1d | https://code.videolan.org/videolan/dav1d.git | export GIT_DAV1D_UPSTREAM=git@xx:yy/dav1d.git | +| 名称 | 默认仓库 | 默认版本 | 使用镜像 | +| ----------- | ---------------------------------------------------- | ------ | -------------------------------------------------------- | +| libass | https://github.com/libass/libass.git | 0.17.1 | export GIT_ASS_UPSTREAM = git@xx:yy/libass.git | +| libbluray | https://code.videolan.org/videolan/libbluray.git | 1.3.4 | export GIT_BLURAY_UPSTREAM = git@xx:yy/libbluray.git | +| dav1d | https://code.videolan.org/videolan/dav1d.git | 1.3.0 | export GIT_DAV1D_UPSTREAM = git@xx:yy/dav1d.git | +| libdvdread | https://code.videolan.org/videolan/libdvdread.git | 6.1.3 | export GIT_DVDREAD_UPSTREAM = git@xx:yy/libdvdread.git | +| FFmpeg | https://github.com/FFmpeg/FFmpeg.git | 5.1.4 | export GIT_FFMPEG_UPSTREAM = git@xx:yy/FFmpeg.git | +| freetype | https://gitlab.freedesktop.org/freetype/freetype.git | 2.13.2 | export GIT_FREETYPE_UPSTREAM = git@xx:yy/freetype.git | +| fribidi | https://github.com/fribidi/fribidi.git | 1.0.13 | export GIT_FRIBIDI_UPSTREAM = git@xx:yy/fribidi.git | +| harfbuzz | https://github.com/harfbuzz/harfbuzz.git | 8.3.0 | export GIT_HARFBUZZ_UPSTREAM = git@xx:yy/harfbuzz.git | +| libyuv | https://github.com/lemenkov/libyuv.git | main | export GIT_LIBYUV_UPSTREAM = git@xx:yy/libyuv.git | +| openssl | https://github.com/openssl/openssl.git | 1.1.1w | export GIT_OPENSSL_UPSTREAM = git@xx:yy/openssl.git | +| opus | https://gitlab.xiph.org/xiph/opus.git | 1.4 | export GIT_OPUS_UPSTREAM = git@xx:yy/opus.git | +| libunibreak | https://github.com/adah1972/libunibreak.git | 5.1 | export GIT_UNIBREAK_UPSTREAM = git@xx:yy/libunibreak.git | ## Advanced Configuration diff --git a/shell/apple/compile-any.sh b/shell/apple/compile-any.sh index 33a1126872..ee6346a659 100755 --- a/shell/apple/compile-any.sh +++ b/shell/apple/compile-any.sh @@ -30,7 +30,7 @@ cd "$THIS_DIR" function usage() { echo " useage:" - echo " $0 [ios|macos] [build|rebuild|lipo|clean] [all|ffmpeg|libyuv|openssl|opus|dav1d|dvdread] [arm64|x86_64|all] [opts...]" + echo " $0 [ios|macos] [build|rebuild|lipo|clean] [all|ffmpeg|libyuv|openssl|opus|bluray|dav1d|dvdread|freetype|fribidi|harfbuzz|unibreak|ass|ffmpeg] [arm64|x86_64|all] [opts...]" } if [[ "$PLAT" != 'ios' && "$PLAT" != 'macos' ]]; then diff --git a/shell/apple/compile-cfgs/ass b/shell/apple/compile-cfgs/ass new file mode 100644 index 0000000000..d54ab4f118 --- /dev/null +++ b/shell/apple/compile-cfgs/ass @@ -0,0 +1,27 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# +# brew install nasm +# If you really want to compile without asm, configure with --disable-asm. + +# LIB_DEPENDS_BIN using string because bash can't export array chttps://stackoverflow.com/questions/5564418/exporting-an-array-in-bash-script +# configure: error: Package requirements (openssl) were not met + +export LIB_NAME='ass' +export LIPO_LIBS="libass" +export LIB_DEPENDS_BIN="automake autoconf libtool" \ No newline at end of file diff --git a/shell/apple/compile-cfgs/freetype b/shell/apple/compile-cfgs/freetype new file mode 100644 index 0000000000..be0c659313 --- /dev/null +++ b/shell/apple/compile-cfgs/freetype @@ -0,0 +1,24 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# +# LIB_DEPENDS_BIN using string because bash can't export array chttps://stackoverflow.com/questions/5564418/exporting-an-array-in-bash-script +# configure: error: Package requirements (openssl) were not met + +export LIB_NAME='freetype' +export LIPO_LIBS="libfreetype" +export LIB_DEPENDS_BIN="meson cmake" diff --git a/shell/apple/compile-cfgs/fribidi b/shell/apple/compile-cfgs/fribidi new file mode 100644 index 0000000000..c2e24a2e56 --- /dev/null +++ b/shell/apple/compile-cfgs/fribidi @@ -0,0 +1,27 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# +# brew install nasm +# If you really want to compile without asm, configure with --disable-asm. + +# LIB_DEPENDS_BIN using string because bash can't export array chttps://stackoverflow.com/questions/5564418/exporting-an-array-in-bash-script +# configure: error: Package requirements (openssl) were not met + +export LIB_NAME='fribidi' +export LIPO_LIBS="libfribidi" +export LIB_DEPENDS_BIN="autoconf automake libtool" \ No newline at end of file diff --git a/shell/apple/compile-cfgs/harfbuzz b/shell/apple/compile-cfgs/harfbuzz new file mode 100644 index 0000000000..0f6a6b1b99 --- /dev/null +++ b/shell/apple/compile-cfgs/harfbuzz @@ -0,0 +1,27 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# +# brew install nasm +# If you really want to compile without asm, configure with --disable-asm. + +# LIB_DEPENDS_BIN using string because bash can't export array chttps://stackoverflow.com/questions/5564418/exporting-an-array-in-bash-script +# configure: error: Package requirements (openssl) were not met + +export LIB_NAME='harfbuzz' +export LIPO_LIBS="libharfbuzz libharfbuzz-subset" +export LIB_DEPENDS_BIN="meson pkg-config" \ No newline at end of file diff --git a/shell/apple/compile-cfgs/list.txt b/shell/apple/compile-cfgs/list.txt index 5564f4d3a6..d549ad003d 100644 --- a/shell/apple/compile-cfgs/list.txt +++ b/shell/apple/compile-cfgs/list.txt @@ -1 +1 @@ -libyuv openssl opus bluray dav1d dvdread ffmpeg \ No newline at end of file +libyuv openssl opus bluray dav1d dvdread freetype fribidi harfbuzz unibreak ass ffmpeg \ No newline at end of file diff --git a/shell/apple/compile-cfgs/unibreak b/shell/apple/compile-cfgs/unibreak new file mode 100644 index 0000000000..679fc8cc00 --- /dev/null +++ b/shell/apple/compile-cfgs/unibreak @@ -0,0 +1,27 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# +# brew install nasm +# If you really want to compile without asm, configure with --disable-asm. + +# LIB_DEPENDS_BIN using string because bash can't export array chttps://stackoverflow.com/questions/5564418/exporting-an-array-in-bash-script +# configure: error: Package requirements (openssl) were not met + +export LIB_NAME='unibreak' +export LIPO_LIBS="libunibreak" +export LIB_DEPENDS_BIN="autoconf automake libtool" \ No newline at end of file diff --git a/shell/apple/do-compile/ass.sh b/shell/apple/do-compile/ass.sh new file mode 100755 index 0000000000..17fed22576 --- /dev/null +++ b/shell/apple/do-compile/ass.sh @@ -0,0 +1,148 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -e + +THIS_DIR=$(DIRNAME=$(dirname "$0"); cd "$DIRNAME"; pwd) +source $THIS_DIR/../../tools/env_assert.sh + +echo "=== [$0] check env begin===" +env_assert "XC_ARCH" +env_assert "XC_BUILD_SOURCE" +env_assert "XC_BUILD_PREFIX" +env_assert "XC_BUILD_NAME" +env_assert "XC_DEPLOYMENT_TARGET" +env_assert "XCRUN_SDK_PATH" +env_assert "XCRUN_CC" +env_assert "THREAD_COUNT" +echo "XC_OPTS:$XC_OPTS" +echo "===check env end===" + +# prepare build config --silent +CFG_FLAGS="--prefix=$XC_BUILD_PREFIX --disable-dependency-tracking --disable-shared --enable-silent-rules --disable-fontconfig --enable-coretext" +CFLAGS="-arch $XC_ARCH $XC_DEPLOYMENT_TARGET $XC_OTHER_CFLAGS" + +# for cross compile +if [[ $(uname -m) != "$XC_ARCH" || "$XC_FORCE_CROSS" ]];then + echo "[*] cross compile, on $(uname -m) compile $XC_PLAT $XC_ARCH." + # https://www.gnu.org/software/automake/manual/html_node/Cross_002dCompilation.html + CFLAGS="$CFLAGS -isysroot $XCRUN_SDK_PATH" + CFG_FLAGS="$CFG_FLAGS --host=$XC_ARCH-apple-darwin --with-sysroot=$XCRUN_SDK_PATH" +fi + +echo "----------------------" +echo "[*] configurate $LIB_NAME" +echo "----------------------" + +cd $XC_BUILD_SOURCE + +if [[ -f 'configure' ]]; then + echo "reuse configure" +else + echo "auto generate configure" + ./autogen.sh 1>/dev/null +fi + +echo + +MY_PKG_CONFIG_LIBDIR='' +# with freetype +if [[ -f "${XC_PRODUCT_ROOT}/freetype-$XC_ARCH/lib/pkgconfig/freetype2.pc" || -f "${XC_PRODUCT_ROOT}/universal/freetype/lib/pkgconfig/freetype2.pc" ]]; then + echo "[*] --enable-freetype" + if [[ -n "$MY_PKG_CONFIG_LIBDIR" ]]; then + MY_PKG_CONFIG_LIBDIR="$MY_PKG_CONFIG_LIBDIR:" + fi + + if [[ -f "${XC_PRODUCT_ROOT}/freetype-$XC_ARCH/lib/pkgconfig/freetype2.pc" ]]; then + MY_PKG_CONFIG_LIBDIR="${MY_PKG_CONFIG_LIBDIR}${XC_PRODUCT_ROOT}/freetype-$XC_ARCH/lib/pkgconfig" + else + MY_PKG_CONFIG_LIBDIR="${MY_PKG_CONFIG_LIBDIR}${XC_PRODUCT_ROOT}/universal/freetype/lib/pkgconfig" + fi +else + echo "[*] --disable-freetype" +fi + +# with fribidi +if [[ -f "${XC_PRODUCT_ROOT}/fribidi-$XC_ARCH/lib/pkgconfig/fribidi.pc" || -f "${XC_PRODUCT_ROOT}/universal/fribidi/lib/pkgconfig/fribidi.pc" ]]; then + echo "[*] --enable-fribidi" + if [[ -n "$MY_PKG_CONFIG_LIBDIR" ]]; then + MY_PKG_CONFIG_LIBDIR="$MY_PKG_CONFIG_LIBDIR:" + fi + + if [[ -f "${XC_PRODUCT_ROOT}/fribidi-$XC_ARCH/lib/pkgconfig/fribidi.pc" ]]; then + MY_PKG_CONFIG_LIBDIR="${MY_PKG_CONFIG_LIBDIR}${XC_PRODUCT_ROOT}/fribidi-$XC_ARCH/lib/pkgconfig" + else + MY_PKG_CONFIG_LIBDIR="${MY_PKG_CONFIG_LIBDIR}${XC_PRODUCT_ROOT}/universal/fribidi/lib/pkgconfig" + fi +else + echo "[*] --disable-fribidi" +fi + +# with harfbuzz +if [[ -f "${XC_PRODUCT_ROOT}/harfbuzz-$XC_ARCH/lib/pkgconfig/harfbuzz.pc" || -f "${XC_PRODUCT_ROOT}/universal/harfbuzz/lib/pkgconfig/harfbuzz.pc" ]]; then + echo "[*] --enable-harfbuzz" + if [[ -n "$MY_PKG_CONFIG_LIBDIR" ]]; then + MY_PKG_CONFIG_LIBDIR="$MY_PKG_CONFIG_LIBDIR:" + fi + + if [[ -f "${XC_PRODUCT_ROOT}/harfbuzz-$XC_ARCH/lib/pkgconfig/harfbuzz.pc" ]]; then + MY_PKG_CONFIG_LIBDIR="${MY_PKG_CONFIG_LIBDIR}${XC_PRODUCT_ROOT}/harfbuzz-$XC_ARCH/lib/pkgconfig" + else + MY_PKG_CONFIG_LIBDIR="${MY_PKG_CONFIG_LIBDIR}${XC_PRODUCT_ROOT}/universal/harfbuzz/lib/pkgconfig" + fi +else + echo "[*] --disable-harfbuzz" +fi + +# with unibreak +if [[ -f "${XC_PRODUCT_ROOT}/unibreak-$XC_ARCH/lib/pkgconfig/libunibreak.pc" || -f "${XC_PRODUCT_ROOT}/universal/unibreak/lib/pkgconfig/libunibreak.pc" ]]; then + echo "[*] --enable-unibreak" + if [[ -n "$MY_PKG_CONFIG_LIBDIR" ]]; then + MY_PKG_CONFIG_LIBDIR="$MY_PKG_CONFIG_LIBDIR:" + fi + + if [[ -f "${XC_PRODUCT_ROOT}/unibreak-$XC_ARCH/lib/pkgconfig/libunibreak.pc" ]]; then + MY_PKG_CONFIG_LIBDIR="${MY_PKG_CONFIG_LIBDIR}${XC_PRODUCT_ROOT}/unibreak-$XC_ARCH/lib/pkgconfig" + else + MY_PKG_CONFIG_LIBDIR="${MY_PKG_CONFIG_LIBDIR}${XC_PRODUCT_ROOT}/universal/unibreak/lib/pkgconfig" + fi +else + echo "[*] --disable-unibreak" +fi + +if [[ -n "$MY_PKG_CONFIG_LIBDIR" ]]; then + export PKG_CONFIG_LIBDIR="$MY_PKG_CONFIG_LIBDIR" +fi + +echo +echo "CC: $XCRUN_CC" +echo "CFG_FLAGS: $CFG_FLAGS" +echo "CFLAGS: $CFLAGS" +echo "PKG_CONFIG_LIBDIR: $MY_PKG_CONFIG_LIBDIR" +echo + +./configure $CFG_FLAGS \ +CC="$XCRUN_CC" \ +CFLAGS="$CFLAGS" \ +LDFLAGS="$CFLAGS" + +#---------------------- +echo "----------------------" +echo "[*] compile $LIB_NAME" +echo "----------------------" + +make install -j$THREAD_COUNT 1>/dev/null \ No newline at end of file diff --git a/shell/apple/do-compile/ffmpeg.sh b/shell/apple/do-compile/ffmpeg.sh index 4fdaa03cee..20f0917d15 100755 --- a/shell/apple/do-compile/ffmpeg.sh +++ b/shell/apple/do-compile/ffmpeg.sh @@ -57,7 +57,9 @@ CFG_FLAGS="$CFG_FLAGS --enable-neon" CFG_FLAGS="$CFG_FLAGS --enable-asm" C_FLAGS= -C_FLAGS="$C_FLAGS -fno-stack-check -arch $XC_ARCH" +# https://gitlab.gnome.org/GNOME/gimp/-/issues/8649 +# from clang 15 int <-> pointer conversions now defaults as an error +C_FLAGS="$C_FLAGS -Wno-int-conversion -fno-stack-check -arch $XC_ARCH" C_FLAGS="$C_FLAGS $XC_DEPLOYMENT_TARGET $XC_OTHER_CFLAGS" if [[ "$XC_OPTS" == "debug" ]]; then diff --git a/shell/apple/do-compile/freetype.sh b/shell/apple/do-compile/freetype.sh new file mode 100755 index 0000000000..513def3948 --- /dev/null +++ b/shell/apple/do-compile/freetype.sh @@ -0,0 +1,73 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -e + +THIS_DIR=$(DIRNAME=$(dirname "$0"); cd "$DIRNAME"; pwd) +source $THIS_DIR/../../tools/env_assert.sh + +echo "=== [$0] check env begin===" +env_assert "XC_ARCH" +env_assert "XC_BUILD_SOURCE" +env_assert "XC_BUILD_PREFIX" +env_assert "XC_BUILD_NAME" +env_assert "XC_DEPLOYMENT_TARGET" +env_assert "XCRUN_SDK_PATH" +env_assert "XCRUN_CC" +echo "XC_OPTS:$XC_OPTS" +echo "===check env end===" + +# prepare build config +CFG_FLAGS="--prefix=$XC_BUILD_PREFIX --default-library static -Dpng=disabled" + +if [[ "$BUILD_OPT" == "debug" ]]; then + CFG_FLAGS="$CFG_FLAGS --buildtype=debug" +else + CFG_FLAGS="$CFG_FLAGS --buildtype=release" +fi + +cd $XC_BUILD_SOURCE +export CC="$XCRUN_CC" +export CXX="$XCRUN_CXX" + +if [[ $(uname -m) != "$XC_ARCH" || "$XC_FORCE_CROSS" ]]; then + echo "[*] cross compile, on $(uname -m) compile $XC_PLAT $XC_ARCH." + CFG_FLAGS="$CFG_FLAGS --cross-file $THIS_DIR/../compile-cfgs/meson-crossfiles/$XC_ARCH-$XC_PLAT.meson" +fi + +echo "----------------------" +echo "[*] compile $LIB_NAME" +echo "CC: $XCRUN_CC" +echo "CFG_FLAGS: $CFG_FLAGS" +echo "----------------------" +echo + +build=./build-$XC_ARCH +if [[ -d $build ]]; then + rm -rf $build +fi + +meson setup $build $CFG_FLAGS + +cd $build + +echo "compile" + +meson compile && meson install + +# ninja -C build +# ninja -C build install diff --git a/shell/apple/do-compile/fribidi.sh b/shell/apple/do-compile/fribidi.sh new file mode 100755 index 0000000000..c53cc4853f --- /dev/null +++ b/shell/apple/do-compile/fribidi.sh @@ -0,0 +1,80 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -e + +THIS_DIR=$(DIRNAME=$(dirname "$0"); cd "$DIRNAME"; pwd) +source $THIS_DIR/../../tools/env_assert.sh + +echo "=== [$0] check env begin===" +env_assert "XC_ARCH" +env_assert "XC_BUILD_SOURCE" +env_assert "XC_BUILD_PREFIX" +env_assert "XC_BUILD_NAME" +env_assert "XC_DEPLOYMENT_TARGET" +env_assert "XCRUN_SDK_PATH" +env_assert "XCRUN_CC" +echo "XC_OPTS:$XC_OPTS" +echo "===check env end===" + +CFG_FLAGS="--prefix=$XC_BUILD_PREFIX --enable-static --disable-shared --silent --enable-silent-rules --disable-dependency-tracking" +CFLAGS="-arch $XC_ARCH $XC_DEPLOYMENT_TARGET $XC_OTHER_CFLAGS" + +# for cross compile +if [[ $(uname -m) != "$XC_ARCH" || "$XC_FORCE_CROSS" ]];then + echo "[*] cross compile, on $(uname -m) compile $XC_PLAT $XC_ARCH." + # https://www.gnu.org/software/automake/manual/html_node/Cross_002dCompilation.html + CFLAGS="$CFLAGS -isysroot $XCRUN_SDK_PATH" + CFG_FLAGS="$CFG_FLAGS --host=$XC_ARCH-apple-darwin --with-sysroot=$XCRUN_SDK_PATH" +fi + +if [[ "$BUILD_OPT" == "debug" ]]; then + CFG_FLAGS="$CFG_FLAGS --enable-debug" +fi + + +cd $XC_BUILD_SOURCE + +if [[ -f 'configure' ]]; then + echo "reuse configure" +else + echo "auto generate configure" + ./autogen.sh +fi + +echo +echo "CC: $XCRUN_CC" +echo "CFG_FLAGS: $CFG_FLAGS" +echo "CFLAGS: $CFLAGS" +echo + +echo "----------------------" +echo "[*] configurate $LIB_NAME" +echo "----------------------" + +./configure $CFG_FLAGS \ + CC="$XCRUN_CC" \ + CFLAGS="$CFLAGS" \ + LDFLAGS="$CFLAGS" + +#---------------------- +echo "----------------------" +echo "[*] compile $LIB_NAME" +echo "----------------------" + +#not support -j8 +make install \ No newline at end of file diff --git a/shell/apple/do-compile/harfbuzz.sh b/shell/apple/do-compile/harfbuzz.sh new file mode 100755 index 0000000000..78df260ef9 --- /dev/null +++ b/shell/apple/do-compile/harfbuzz.sh @@ -0,0 +1,98 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# https://github.com/harfbuzz/harfbuzz/blob/main/BUILD.md + +# https://trac.macports.org/ticket/60987 + +set -e + +THIS_DIR=$(DIRNAME=$(dirname "$0"); cd "$DIRNAME"; pwd) +source $THIS_DIR/../../tools/env_assert.sh + +echo "=== [$0] check env begin===" +env_assert "XC_ARCH" +env_assert "XC_BUILD_SOURCE" +env_assert "XC_BUILD_PREFIX" +env_assert "XC_BUILD_NAME" +env_assert "XC_DEPLOYMENT_TARGET" +env_assert "XCRUN_SDK_PATH" +env_assert "XCRUN_CC" +echo "XC_OPTS:$XC_OPTS" +echo "===check env end===" + +# prepare build config +CFG_FLAGS="--prefix=$XC_BUILD_PREFIX --default-library static" + +if [[ "$BUILD_OPT" == "debug" ]]; then + CFG_FLAGS="$CFG_FLAGS --buildtype=debug" +else + CFG_FLAGS="$CFG_FLAGS --buildtype=release" +fi + +MY_PKG_CONFIG_LIBDIR='' +# with freetype +if [[ -f "${XC_PRODUCT_ROOT}/freetype-$XC_ARCH/lib/pkgconfig/freetype2.pc" || -f "${XC_PRODUCT_ROOT}/universal/freetype/lib/pkgconfig/freetype2.pc" ]]; then + echo "[*] --enable-freetype" + if [[ -n "$MY_PKG_CONFIG_LIBDIR" ]]; then + MY_PKG_CONFIG_LIBDIR="$MY_PKG_CONFIG_LIBDIR:" + fi + + if [[ -f "${XC_PRODUCT_ROOT}/freetype-$XC_ARCH/lib/pkgconfig/freetype2.pc" ]]; then + MY_PKG_CONFIG_LIBDIR="${MY_PKG_CONFIG_LIBDIR}${XC_PRODUCT_ROOT}/freetype-$XC_ARCH/lib/pkgconfig" + else + MY_PKG_CONFIG_LIBDIR="${MY_PKG_CONFIG_LIBDIR}${XC_PRODUCT_ROOT}/universal/freetype/lib/pkgconfig" + fi +else + echo "[*] --disable-freetype" +fi + +if [[ -n "$MY_PKG_CONFIG_LIBDIR" ]]; then + export PKG_CONFIG_LIBDIR="$MY_PKG_CONFIG_LIBDIR" +fi + +cd $XC_BUILD_SOURCE +export CC="$XCRUN_CC" +export CXX="$XCRUN_CXX" + +if [[ $(uname -m) != "$XC_ARCH" || "$XC_FORCE_CROSS" ]]; then + echo "[*] cross compile, on $(uname -m) compile $XC_PLAT $XC_ARCH." + CFG_FLAGS="$CFG_FLAGS --cross-file $THIS_DIR/../compile-cfgs/meson-crossfiles/$XC_ARCH-$XC_PLAT.meson" + export PKG_CONFIG=$(which pkg-config) +fi + +echo "----------------------" +echo "[*] compile $LIB_NAME" +echo "CC: $XCRUN_CC" +echo "CFG_FLAGS: $CFG_FLAGS" +echo "PKG_CONFIG_LIBDIR: $MY_PKG_CONFIG_LIBDIR" +echo "----------------------" +echo + +build=./build-$XC_ARCH +if [[ -d $build ]]; then + rm -rf $build +fi + +meson setup $build $CFG_FLAGS 1>/dev/null + +cd $build +# show all configure +# https://mesonbuild.com/Build-options.html +# meson configure +meson configure -Ddocs=disabled -Dcairo=disabled -Dchafa=disabled -Dfreetype=enabled -Dtests=disabled +meson compile +meson install 1>/dev/null \ No newline at end of file diff --git a/shell/apple/do-compile/libyuv.sh b/shell/apple/do-compile/libyuv.sh index 2a686fe99d..7c2d1a73e6 100755 --- a/shell/apple/do-compile/libyuv.sh +++ b/shell/apple/do-compile/libyuv.sh @@ -58,7 +58,7 @@ echo "----------------------" echo "[*] compile libyuv" echo "----------------------" -make -f linux.mk CC="$XCRUN_CC" CXX="$XCRUN_CXX" CFLAGS="$CFLAGS" CXXFLAGS="$CFLAGS" -j$THREAD_COUNT >/dev/null +CC="$XCRUN_CC" CXX="$XCRUN_CXX" CFLAGS="$CFLAGS" CXXFLAGS="$CFLAGS" make -f linux.mk libyuv.a -j$THREAD_COUNT >/dev/null mkdir -p "${XC_BUILD_PREFIX}/lib" cp libyuv.a "${XC_BUILD_PREFIX}/lib" diff --git a/shell/apple/do-compile/openssl.sh b/shell/apple/do-compile/openssl.sh index 33277f92ca..62e9660661 100755 --- a/shell/apple/do-compile/openssl.sh +++ b/shell/apple/do-compile/openssl.sh @@ -81,5 +81,6 @@ echo "----------------------" echo "[*] compile $LIB_NAME" echo "----------------------" set +e -make -j$THREAD_COUNT >/dev/null -make install_sw >/dev/null + +make build_libs -j$THREAD_COUNT >/dev/null +make install_dev >/dev/null diff --git a/shell/apple/do-compile/unibreak.sh b/shell/apple/do-compile/unibreak.sh new file mode 100755 index 0000000000..85ab8181dd --- /dev/null +++ b/shell/apple/do-compile/unibreak.sh @@ -0,0 +1,72 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -e + +THIS_DIR=$(DIRNAME=$(dirname "$0"); cd "$DIRNAME"; pwd) +source $THIS_DIR/../../tools/env_assert.sh + +echo "=== [$0] check env begin===" +env_assert "XC_ARCH" +env_assert "XC_BUILD_SOURCE" +env_assert "XC_BUILD_PREFIX" +env_assert "XC_BUILD_NAME" +env_assert "XC_DEPLOYMENT_TARGET" +env_assert "XCRUN_SDK_PATH" +env_assert "XCRUN_CC" +env_assert "THREAD_COUNT" +echo "XC_OPTS:$XC_OPTS" +echo "===check env end===" + +CFG_FLAGS="--prefix=$XC_BUILD_PREFIX --enable-static --disable-shared --silent" +CFLAGS="-arch $XC_ARCH $XC_DEPLOYMENT_TARGET $XC_OTHER_CFLAGS" + +# for cross compile +if [[ $(uname -m) != "$XC_ARCH" || "$XC_FORCE_CROSS" ]];then + echo "[*] cross compile, on $(uname -m) compile $XC_PLAT $XC_ARCH." + # https://www.gnu.org/software/automake/manual/html_node/Cross_002dCompilation.html + CFLAGS="$CFLAGS -isysroot $XCRUN_SDK_PATH" + CFG_FLAGS="$CFG_FLAGS --host=$XC_ARCH-apple-darwin --with-sysroot=$XCRUN_SDK_PATH" +fi + +cd $XC_BUILD_SOURCE + +echo +echo "CC: $XCRUN_CC" +echo "CFG_FLAGS: $CFG_FLAGS" +echo "CFLAGS: $CFLAGS" +echo + +echo "----------------------" +echo "[*] configurate $LIB_NAME" +echo "----------------------" + +echo "generate configure" + +./autogen.sh 1>/dev/null + +./configure $CFG_FLAGS \ + CC="$XCRUN_CC" \ + CFLAGS="$CFLAGS" \ + LDFLAGS="$CFLAGS" 1>/dev/null + +#---------------------- +echo "----------------------" +echo "[*] compile $LIB_NAME" +echo "----------------------" + +make -j$THREAD_COUNT install 1>/dev/null \ No newline at end of file diff --git a/shell/download-pre.sh b/shell/download-pre.sh deleted file mode 100755 index ba8be03187..0000000000 --- a/shell/download-pre.sh +++ /dev/null @@ -1,75 +0,0 @@ -#! /usr/bin/env bash -# -# Copyright (C) 2022 Matt Reach - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -set -e - -PLAT=$1 -VER=$2 - -if test -z $PLAT ;then - PLAT='all' -fi - -THIS_DIR=$(DIRNAME=$(dirname "$0"); cd "$DIRNAME"; pwd) -cd "$THIS_DIR" - -function usage() { - echo "=== useage ====================" - echo "Download precompiled libs from github,The usage is as follows:" - echo "$0 [ios|macos|all] []" -} - -function download() { - local plat=$1 - echo "===[download $plat $VER]====================" - mkdir -p build/pre - cd build/pre - local fname="$plat-universal-$VER.zip" - local url="https://github.com/debugly/MRFFToolChainBuildShell/releases/download/$VER/$fname" - echo "$url" - curl -LO "$url" - mkdir -p ../product/$plat/universal - unzip -oq $fname -d ../product/$plat/universal - if command -v tree >/dev/null 2>&1; then - tree -L 2 ../product/$plat/universal - fi - echo "====================================" - cd - >/dev/null -} - -if [[ "$PLAT" != 'ios' && "$PLAT" != 'macos' && "$PLAT" != 'all' ]]; then - echo 'wrong plat,use ios or macos or all!' - usage - exit -fi - -if test -z $VER ;then - #VER=$(git describe --abbrev=0 --tag | awk -F - '{printf "%s-%s",$1,$2}') - usage - exit -fi - -if [[ "$PLAT" == 'ios' || "$PLAT" == 'macos' ]]; then - download $PLAT -elif [[ "$PLAT" == 'all' ]]; then - plats="ios macos" - for plat in $plats; do - download $plat - done -else - usage -fi diff --git a/shell/ffconfig/module-full.sh b/shell/ffconfig/module-full.sh index 49e21496fb..cb506b5ad2 100755 --- a/shell/ffconfig/module-full.sh +++ b/shell/ffconfig/module-full.sh @@ -325,6 +325,7 @@ export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=dvdsub" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=dvbsub" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=text" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=movtext" +export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=microdvd" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=ass" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=webvtt" @@ -549,5 +550,6 @@ export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-sdl2" # export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-random=LIST" # export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --random-seed=VALUE" export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-linux-perf" -export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-bzlib" +# enable http gzip、deflate +export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-zlib" diff --git a/shell/init-any.sh b/shell/init-any.sh index e8f4c719e1..ff6e8ca581 100755 --- a/shell/init-any.sh +++ b/shell/init-any.sh @@ -35,7 +35,7 @@ cd "$THIS_DIR" function usage() { echo " useage:" - echo " $0 [ios,macos,all] [all|fdk-aac|ffmpeg|lame|libyuv|openssl|opus|x264|bluray] [all,arm64,x86_64]" + echo " $0 [ios,macos,all] [all|ffmpeg|libyuv|openssl|opus|bluray|dvdread|dav1d|freetype|harfbuzz|fribidi|unibreak|ass|ffmpeg] [all,arm64,x86_64]" } if [[ "$SKIP_PULL_BASE" ]];then diff --git a/shell/init-cfgs/ass b/shell/init-cfgs/ass new file mode 100644 index 0000000000..89ef3da144 --- /dev/null +++ b/shell/init-cfgs/ass @@ -0,0 +1,28 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# you can export GIT_ASS_UPSTREAM=git@xx:yy/ASS.git use your mirror +if [[ "$GIT_ASS_UPSTREAM" != "" ]] ;then + export GIT_UPSTREAM="$GIT_ASS_UPSTREAM" +else + export GIT_UPSTREAM=https://github.com/libass/libass.git +fi + +export GIT_LOCAL_REPO=build/extra/ass +export GIT_COMMIT=0.17.1 +export REPO_DIR=ass +export GIT_REPO_VERSION=0.17.1 \ No newline at end of file diff --git a/shell/init-cfgs/freetype b/shell/init-cfgs/freetype new file mode 100644 index 0000000000..17cd42c5e0 --- /dev/null +++ b/shell/init-cfgs/freetype @@ -0,0 +1,29 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# you can export GIT_FREETYPE_UPSTREAM=git@xx:yy/FREETYPE.git use your mirror +if [[ "$GIT_FREETYPE_UPSTREAM" != "" ]] ;then + export GIT_UPSTREAM="$GIT_FREETYPE_UPSTREAM" +else + export GIT_UPSTREAM=https://gitlab.freedesktop.org/freetype/freetype.git +fi + +export GIT_LOCAL_REPO=build/extra/freetype +export GIT_COMMIT=VER-2-13-2 +export REPO_DIR=freetype +export GIT_REPO_VERSION=2.13.2 +export GIT_WITH_SUBMODULE=1 \ No newline at end of file diff --git a/shell/init-cfgs/fribidi b/shell/init-cfgs/fribidi new file mode 100644 index 0000000000..3f7e1d5ddc --- /dev/null +++ b/shell/init-cfgs/fribidi @@ -0,0 +1,28 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# you can export GIT_FRIBIDI_UPSTREAM=git@xx:yy/FRIBIDI.git use your mirror +if [[ "$GIT_FRIBIDI_UPSTREAM" != "" ]] ;then + export GIT_UPSTREAM="$GIT_FRIBIDI_UPSTREAM" +else + export GIT_UPSTREAM=https://github.com/fribidi/fribidi.git +fi + +export GIT_LOCAL_REPO=build/extra/fribidi +export GIT_COMMIT=v1.0.13 +export REPO_DIR=fribidi +export GIT_REPO_VERSION=1.0.13 \ No newline at end of file diff --git a/shell/init-cfgs/harfbuzz b/shell/init-cfgs/harfbuzz new file mode 100644 index 0000000000..f88e6f8546 --- /dev/null +++ b/shell/init-cfgs/harfbuzz @@ -0,0 +1,28 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# you can export GIT_HARFBUZZ_UPSTREAM=git@xx:yy/HARFBUZZ.git use your mirror +if [[ "$GIT_HARFBUZZ_UPSTREAM" != "" ]] ;then + export GIT_UPSTREAM="$GIT_HARFBUZZ_UPSTREAM" +else + export GIT_UPSTREAM=https://github.com/harfbuzz/harfbuzz.git +fi + +export GIT_LOCAL_REPO=build/extra/harfbuzz +export GIT_COMMIT=8.3.0 +export REPO_DIR=harfbuzz +export GIT_REPO_VERSION=8.3.0 \ No newline at end of file diff --git a/shell/init-cfgs/unibreak b/shell/init-cfgs/unibreak new file mode 100644 index 0000000000..a111911b69 --- /dev/null +++ b/shell/init-cfgs/unibreak @@ -0,0 +1,28 @@ +#! /usr/bin/env bash +# +# Copyright (C) 2021 Matt Reach + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# you can export GIT_UNIBREAK_UPSTREAM=git@xx:yy/UNIBREAK.git use your mirror +if [[ "$GIT_UNIBREAK_UPSTREAM" != "" ]] ;then + export GIT_UPSTREAM="$GIT_UNIBREAK_UPSTREAM" +else + export GIT_UPSTREAM=https://github.com/adah1972/libunibreak.git +fi + +export GIT_LOCAL_REPO=build/extra/unibreak +export GIT_COMMIT=libunibreak_5_1 +export REPO_DIR=unibreak +export GIT_REPO_VERSION=5.1 \ No newline at end of file diff --git a/shell/install-pre-any.sh b/shell/install-pre-any.sh index 19c68f4c0e..018f246801 100755 --- a/shell/install-pre-any.sh +++ b/shell/install-pre-any.sh @@ -29,7 +29,13 @@ MAC_BLURAY_TAG='bluray-1.3.4-240108180408' DAV1D_TAG='dav1d-1.3.0-231127183948' OPENSSL_TAG='openssl-1.1.1w-231127183927' DVDREAD_TAG='dvdread-6.1.3-240108102425' -FFMPEG_TAG='ffmpeg-5.1.4-240108181435' +FFMPEG_TAG='ffmpeg-5.1.4-240510145103' + +FREETYPE_TAG='freetype-2.13.2-240320173506' +UNIBREAK_TAG='unibreak-5.1-231229171455' +FRIBIDI_TAG='fribidi-1.0.13-240320172504' +HARFBUZZ_TAG='harfbuzz-8.3.0-240320182151' +ASS_TAG='ass-0.17.1-240320183602' #---------------------------------------------------------- set -e @@ -55,7 +61,7 @@ function install_lib () function usage() { echo "=== useage ====================" echo "Download pre-compiled libs from github:" - echo " $0 [ios,macos,all] [all|ffmpeg|libyuv|openssl|opus|bluray|dav1d]" + echo " $0 [ios,macos,all] [all|ffmpeg|libyuv|openssl|opus|bluray|dav1d|freetype|fribidi|harfbuzz|unibreak|ass]" exit 1 } @@ -101,6 +107,21 @@ if [[ "$PLAT" == 'ios' || "$PLAT" == 'macos' || "$PLAT" == 'all' ]]; then 'dvdread') TAG=$DVDREAD_TAG ;; + 'freetype') + TAG=$FREETYPE_TAG + ;; + 'harfbuzz') + TAG=$HARFBUZZ_TAG + ;; + 'fribidi') + TAG=$FRIBIDI_TAG + ;; + 'unibreak') + TAG=$UNIBREAK_TAG + ;; + 'ass') + TAG=$ASS_TAG + ;; *) echo "wrong lib name:$lib" usage diff --git a/shell/patches/ffmpeg-release-5.1/0001-h264_ps-null-pointer-fault-tolerant.patch b/shell/patches/ffmpeg-release-5.1/0001-15-h264_ps-null-pointer-fault-tolerant.patch similarity index 97% rename from shell/patches/ffmpeg-release-5.1/0001-h264_ps-null-pointer-fault-tolerant.patch rename to shell/patches/ffmpeg-release-5.1/0001-15-h264_ps-null-pointer-fault-tolerant.patch index 0ab76b5a24..c3b8d71a67 100644 --- a/shell/patches/ffmpeg-release-5.1/0001-h264_ps-null-pointer-fault-tolerant.patch +++ b/shell/patches/ffmpeg-release-5.1/0001-15-h264_ps-null-pointer-fault-tolerant.patch @@ -1,4 +1,4 @@ -From b0ae2afaabf62c0ce3e37753621f6f0669b054a0 Mon Sep 17 00:00:00 2001 +From 8d2dac9be4b04467c7d6bf4b4d3785adb8482a3f Mon Sep 17 00:00:00 2001 From: qianlongxu Date: Mon, 22 May 2023 17:21:37 +0800 Subject: [PATCH 01/15] h264_ps null pointer fault tolerant diff --git a/shell/patches/ffmpeg-release-5.1/0002-flv-support-hevc.patch b/shell/patches/ffmpeg-release-5.1/0002-flv-support-hevc.patch index c532b31ebb..51a87b60e1 100644 --- a/shell/patches/ffmpeg-release-5.1/0002-flv-support-hevc.patch +++ b/shell/patches/ffmpeg-release-5.1/0002-flv-support-hevc.patch @@ -1,4 +1,4 @@ -From c9eee98fb63278c87a3e81fde5cce5de688ef95c Mon Sep 17 00:00:00 2001 +From 7b857db7e5c334b873ca7a113b47a35e5b1106e0 Mon Sep 17 00:00:00 2001 From: qianlongxu Date: Mon, 22 May 2023 17:27:59 +0800 Subject: [PATCH 02/15] flv support hevc diff --git a/shell/patches/ffmpeg-release-5.1/0003-dict-add-more-util-method.patch b/shell/patches/ffmpeg-release-5.1/0003-dict-add-more-util-method.patch deleted file mode 100644 index 453663ecae..0000000000 --- a/shell/patches/ffmpeg-release-5.1/0003-dict-add-more-util-method.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 3a90f5a893e5042c0a319c8f50eee6a1ad29de8e Mon Sep 17 00:00:00 2001 -From: qianlongxu -Date: Fri, 29 Jul 2022 12:50:10 +0800 -Subject: [PATCH 03/15] dict add more util method - ---- - libavutil/dict.c | 39 ++++++++++++++++++++++++++++++++++++++- - libavutil/dict.h | 10 ++++++++++ - 2 files changed, 48 insertions(+), 1 deletion(-) - -diff --git a/libavutil/dict.c b/libavutil/dict.c -index 9d3d96c..9ec8dfa 100644 ---- a/libavutil/dict.c -+++ b/libavutil/dict.c -@@ -42,7 +42,7 @@ AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key, - { - unsigned int i, j; - -- if (!m) -+ if (!m || !key) - return NULL; - - if (prev) -@@ -153,6 +153,43 @@ int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, - return av_dict_set(pm, key, valuestr, flags); - } - -+int av_dict_set_intptr(AVDictionary **pm, const char *key, uintptr_t value, -+ int flags) -+{ -+ char valuestr[22]; -+ snprintf(valuestr, sizeof(valuestr), "%p", value); -+ flags &= ~AV_DICT_DONT_STRDUP_VAL; -+ return av_dict_set(pm, key, valuestr, flags); -+} -+ -+uintptr_t av_dict_get_intptr(const AVDictionary *m, const char* key) { -+ uintptr_t ptr = NULL; -+ AVDictionaryEntry *t = NULL; -+ if ((t = av_dict_get(m, key, NULL, 0))) { -+ return av_dict_strtoptr(t->value); -+ } -+ return NULL; -+} -+ -+uintptr_t av_dict_strtoptr(char * value) { -+ uintptr_t ptr = NULL; -+ char *next = NULL; -+ if(!value || value[0] !='0' || (value[1]|0x20)!='x') { -+ return NULL; -+ } -+ ptr = strtoull(value, &next, 16); -+ if (next == value) { -+ return NULL; -+ } -+ return ptr; -+} -+ -+char * av_dict_ptrtostr(uintptr_t value) { -+ char valuestr[22] = {0}; -+ snprintf(valuestr, sizeof(valuestr), "%p", value); -+ return av_strdup(valuestr); -+} -+ - static int parse_key_value_pair(AVDictionary **pm, const char **buf, - const char *key_val_sep, const char *pairs_sep, - int flags) -diff --git a/libavutil/dict.h b/libavutil/dict.h -index 0d1afc6..d200b42 100644 ---- a/libavutil/dict.h -+++ b/libavutil/dict.h -@@ -135,6 +135,16 @@ int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags - */ - int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, int flags); - -+/** -+ * Convenience wrapper for av_dict_get that converts the value to a pointer -+ * and stores it. -+ * -+ */ -+int av_dict_set_intptr(AVDictionary **pm, const char *key, uintptr_t value, int flags); -+uintptr_t av_dict_get_intptr(const AVDictionary *m, const char* key); -+uintptr_t av_dict_strtoptr(char * value); -+char * av_dict_ptrtostr(uintptr_t value); -+ - /** - * Parse the key/value pairs list and add the parsed entries to a dictionary. - * --- -2.39.3 (Apple Git-145) - diff --git a/shell/patches/ffmpeg-release-5.1/0004-file-use-file_seek.patch b/shell/patches/ffmpeg-release-5.1/0003-file-use-file_seek.patch similarity index 89% rename from shell/patches/ffmpeg-release-5.1/0004-file-use-file_seek.patch rename to shell/patches/ffmpeg-release-5.1/0003-file-use-file_seek.patch index cedf70bb8d..7e5a1c3e5b 100644 --- a/shell/patches/ffmpeg-release-5.1/0004-file-use-file_seek.patch +++ b/shell/patches/ffmpeg-release-5.1/0003-file-use-file_seek.patch @@ -1,7 +1,7 @@ -From 7b54df2c93d327611d594f9b4ea1475bcf729cb6 Mon Sep 17 00:00:00 2001 +From 1386ccfc25e133406619c6500c19f2e0bd425e79 Mon Sep 17 00:00:00 2001 From: qianlongxu Date: Fri, 29 Jul 2022 12:54:09 +0800 -Subject: [PATCH 04/15] file use file_seek +Subject: [PATCH 03/15] file use file_seek --- libavformat/file.c | 3 ++- diff --git a/shell/patches/ffmpeg-release-5.1/0004-restore-ijk-custom-protocols-except-long.patch b/shell/patches/ffmpeg-release-5.1/0004-restore-ijk-custom-protocols-except-long.patch new file mode 100644 index 0000000000..c653124267 --- /dev/null +++ b/shell/patches/ffmpeg-release-5.1/0004-restore-ijk-custom-protocols-except-long.patch @@ -0,0 +1,289 @@ +From 3c3b8cd1efb6e28fccd4b964531c21bf0fc94c26 Mon Sep 17 00:00:00 2001 +From: qianlongxu +Date: Tue, 23 Jan 2024 15:32:33 +0800 +Subject: [PATCH 04/15] restore ijk custom protocols except long url + +--- + libavcodec/Makefile | 1 + + libavformat/Makefile | 9 ++++ + libavformat/allformats.c | 3 ++ + libavformat/async.c | 2 +- + libavformat/avformat.h | 5 +++ + libavformat/demux.c | 17 ++++++- + libavformat/ijkutils.c | 97 ++++++++++++++++++++++++++++++++++++++++ + libavformat/protocols.c | 6 +++ + libavutil/Makefile | 1 + + 9 files changed, 139 insertions(+), 2 deletions(-) + create mode 100644 libavformat/ijkutils.c + +diff --git a/libavcodec/Makefile b/libavcodec/Makefile +index 457ec58..81f2d80 100644 +--- a/libavcodec/Makefile ++++ b/libavcodec/Makefile +@@ -26,6 +26,7 @@ HEADERS = ac3_parser.h \ + videotoolbox.h \ + vorbis_parser.h \ + xvmc.h \ ++ packet_internal.h \ + + OBJS = ac3_parser.o \ + adts_parser.o \ +diff --git a/libavformat/Makefile b/libavformat/Makefile +index 6c6b779..dfbed75 100644 +--- a/libavformat/Makefile ++++ b/libavformat/Makefile +@@ -5,6 +5,14 @@ HEADERS = avformat.h \ + avio.h \ + version.h \ + version_major.h \ ++ avc.h \ ++ url.h \ ++ internal.h \ ++ avio_internal.h \ ++ flv.h \ ++ id3v2.h \ ++ os_support.h \ ++ metadata.h \ + + OBJS = allformats.o \ + avformat.o \ +@@ -29,6 +37,7 @@ OBJS = allformats.o \ + url.o \ + utils.o \ + version.o \ ++ ijkutils.o \ + + OBJS-$(HAVE_LIBC_MSVCRT) += file_open.o + +diff --git a/libavformat/allformats.c b/libavformat/allformats.c +index 32698b8..17014e5 100644 +--- a/libavformat/allformats.c ++++ b/libavformat/allformats.c +@@ -544,6 +544,9 @@ extern const AVInputFormat ff_libmodplug_demuxer; + extern const AVInputFormat ff_libopenmpt_demuxer; + extern const AVInputFormat ff_vapoursynth_demuxer; + ++extern AVInputFormat ff_ijklivehook_demuxer; ++extern AVInputFormat ff_ijklas_demuxer; ++ + #include "libavformat/muxer_list.c" + #include "libavformat/demuxer_list.c" + +diff --git a/libavformat/async.c b/libavformat/async.c +index 3c6f89c..8c35dd0 100644 +--- a/libavformat/async.c ++++ b/libavformat/async.c +@@ -487,7 +487,7 @@ static const AVClass async_context_class = { + .version = LIBAVUTIL_VERSION_INT, + }; + +-const URLProtocol ff_async_protocol = { ++URLProtocol ff_async_protocol = { + .name = "async", + .url_open2 = async_open, + .url_read = async_read, +diff --git a/libavformat/avformat.h b/libavformat/avformat.h +index f12fa7d..f403aa7 100644 +--- a/libavformat/avformat.h ++++ b/libavformat/avformat.h +@@ -728,6 +728,11 @@ typedef struct AVInputFormat { + */ + int (*read_header)(struct AVFormatContext *); + ++ /** ++ * Used by format which open further nested input. ++ */ ++ int (*read_header2)(struct AVFormatContext *, AVDictionary **options); ++ + /** + * Read one packet and put it in 'pkt'. pts and flags are also + * set. 'avformat_new_stream' can be called only if the flag +diff --git a/libavformat/demux.c b/libavformat/demux.c +index 1620716..06216eb 100644 +--- a/libavformat/demux.c ++++ b/libavformat/demux.c +@@ -223,6 +223,7 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, + AVFormatContext *s = *ps; + FFFormatContext *si; + AVDictionary *tmp = NULL; ++ AVDictionary *tmp2 = NULL; + ID3v2ExtraMeta *id3v2_extra_meta = NULL; + int ret = 0; + +@@ -306,12 +307,24 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, + if (s->pb) + ff_id3v2_read_dict(s->pb, &si->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); + +- if (s->iformat->read_header) ++ if (s->iformat->read_header2) { ++ if (options) ++ av_dict_copy(&tmp2,*options, 0); ++ if ((ret = s->iformat->read_header2(s, &tmp2)) < 0) { ++ if (s->iformat->flags_internal & FF_FMT_INIT_CLEANUP) ++ goto close; ++ goto fail; ++ } ++ } else if (s->iformat->read_header) { + if ((ret = s->iformat->read_header(s)) < 0) { + if (s->iformat->flags_internal & FF_FMT_INIT_CLEANUP) + goto close; + goto fail; + } ++ } else { ++ av_log(s, AV_LOG_ERROR, "wtf? iformat not implement read_header.\n"); ++ goto fail; ++ } + + if (!s->metadata) { + s->metadata = si->id3v2_meta; +@@ -348,6 +361,7 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, + if (options) { + av_dict_free(options); + *options = tmp; ++ av_dict_free(&tmp2); + } + *ps = s; + return 0; +@@ -358,6 +372,7 @@ close: + fail: + ff_id3v2_free_extra_meta(&id3v2_extra_meta); + av_dict_free(&tmp); ++ av_dict_free(&tmp2); + if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO)) + avio_closep(&s->pb); + avformat_free_context(s); +diff --git a/libavformat/ijkutils.c b/libavformat/ijkutils.c +new file mode 100644 +index 0000000..25faad1 +--- /dev/null ++++ b/libavformat/ijkutils.c +@@ -0,0 +1,97 @@ ++/* ++ * utils.c ++ * ++ * Copyright (c) 2003 Fabrice Bellard ++ * Copyright (c) 2013 Zhang Rui ++ * ++ * This file is part of ijkPlayer. ++ * ++ * ijkPlayer is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * ijkPlayer is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with ijkPlayer; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include ++#include "url.h" ++#include "avformat.h" ++ ++ ++#define IJK_FF_PROTOCOL(x) \ ++extern URLProtocol ff_##x##_protocol; \ ++int ijkav_register_##x##_protocol(URLProtocol *protocol, int protocol_size); \ ++int ijkav_register_##x##_protocol(URLProtocol *protocol, int protocol_size) \ ++{ \ ++ if (protocol_size != sizeof(URLProtocol)) { \ ++ av_log(NULL, AV_LOG_ERROR, "ijkav_register_##x##_protocol: ABI mismatch.\n"); \ ++ return -1; \ ++ } \ ++ memcpy(&ff_##x##_protocol, protocol, protocol_size); \ ++ return 0; \ ++} ++ ++#define IJK_DUMMY_PROTOCOL(x) \ ++IJK_FF_PROTOCOL(x); \ ++static const AVClass ijk_##x##_context_class = { \ ++ .class_name = #x, \ ++ .item_name = av_default_item_name, \ ++ .version = LIBAVUTIL_VERSION_INT, \ ++ }; \ ++ \ ++URLProtocol ff_##x##_protocol = { \ ++ .name = #x, \ ++ .url_open2 = ijkdummy_open, \ ++ .priv_data_size = 1, \ ++ .priv_data_class = &ijk_##x##_context_class, \ ++}; ++ ++static int ijkdummy_open(URLContext *h, const char *arg, int flags, AVDictionary **options) ++{ ++ return -1; ++} ++ ++IJK_FF_PROTOCOL(async); ++IJK_DUMMY_PROTOCOL(ijkmediadatasource); ++IJK_DUMMY_PROTOCOL(ijkhttphook); ++IJK_DUMMY_PROTOCOL(ijksegment); ++IJK_DUMMY_PROTOCOL(ijktcphook); ++IJK_DUMMY_PROTOCOL(ijkio); ++ ++#define IJK_FF_DEMUXER(x) \ ++extern AVInputFormat ff_##x##_demuxer; \ ++int ijkav_register_##x##_demuxer(AVInputFormat *demuxer, int demuxer_size); \ ++int ijkav_register_##x##_demuxer(AVInputFormat *demuxer, int demuxer_size) \ ++{ \ ++ if (demuxer_size != sizeof(AVInputFormat)) { \ ++ av_log(NULL, AV_LOG_ERROR, "ijkav_register_##x##_demuxer: ABI mismatch.\n"); \ ++ return -1; \ ++ } \ ++ memcpy(&ff_##x##_demuxer, demuxer, demuxer_size); \ ++ return 0; \ ++} ++ ++#define IJK_DUMMY_DEMUXER(x) \ ++IJK_FF_DEMUXER(x); \ ++static const AVClass ijk_##x##_demuxer_class = { \ ++ .class_name = #x, \ ++ .item_name = av_default_item_name, \ ++ .version = LIBAVUTIL_VERSION_INT, \ ++ }; \ ++ \ ++AVInputFormat ff_##x##_demuxer = { \ ++ .name = #x, \ ++ .priv_data_size = 1, \ ++ .priv_class = &ijk_##x##_demuxer_class, \ ++}; ++ ++IJK_DUMMY_DEMUXER(ijklivehook); ++IJK_DUMMY_DEMUXER(ijklas); +diff --git a/libavformat/protocols.c b/libavformat/protocols.c +index 6ee62a5..8c67f34 100644 +--- a/libavformat/protocols.c ++++ b/libavformat/protocols.c +@@ -74,6 +74,12 @@ extern const URLProtocol ff_libzmq_protocol; + extern const URLProtocol ff_ipfs_protocol; + extern const URLProtocol ff_ipns_protocol; + ++extern const URLProtocol ff_ijkhttphook_protocol; ++extern const URLProtocol ff_ijkmediadatasource_protocol; ++extern const URLProtocol ff_ijksegment_protocol; ++extern const URLProtocol ff_ijktcphook_protocol; ++extern const URLProtocol ff_ijkio_protocol; ++ + #include "libavformat/protocol_list.c" + + const AVClass *ff_urlcontext_child_class_iterate(void **iter) +diff --git a/libavutil/Makefile b/libavutil/Makefile +index 9435a0b..2744693 100644 +--- a/libavutil/Makefile ++++ b/libavutil/Makefile +@@ -78,6 +78,7 @@ HEADERS = adler32.h \ + spherical.h \ + stereo3d.h \ + threadmessage.h \ ++ thread.h \ + time.h \ + timecode.h \ + timestamp.h \ +-- +2.39.3 (Apple Git-145) + diff --git a/shell/patches/ffmpeg-release-5.1/0008-hls-support-discontinuity.patch b/shell/patches/ffmpeg-release-5.1/0005-hls-support-discontinuity.patch similarity index 98% rename from shell/patches/ffmpeg-release-5.1/0008-hls-support-discontinuity.patch rename to shell/patches/ffmpeg-release-5.1/0005-hls-support-discontinuity.patch index 37d562b672..b9fc377c3b 100644 --- a/shell/patches/ffmpeg-release-5.1/0008-hls-support-discontinuity.patch +++ b/shell/patches/ffmpeg-release-5.1/0005-hls-support-discontinuity.patch @@ -1,7 +1,7 @@ -From c69fdc7f7a8d450f6480f52fb3bea670f8c6cbd1 Mon Sep 17 00:00:00 2001 +From ad70425a954678c0cbcb779b15d2bc3cd0d36f0f Mon Sep 17 00:00:00 2001 From: qianlongxu Date: Mon, 12 Jun 2023 16:15:15 +0800 -Subject: [PATCH 08/15] hls support discontinuity +Subject: [PATCH 05/15] hls support discontinuity --- libavformat/hls.c | 60 +++++++++++++++++++++++++++++++++++++++++------ diff --git a/shell/patches/ffmpeg-release-5.1/0006-restore-ijk-http-event-hooks.patch b/shell/patches/ffmpeg-release-5.1/0006-restore-ijk-http-event-hooks.patch new file mode 100644 index 0000000000..63a05092a0 --- /dev/null +++ b/shell/patches/ffmpeg-release-5.1/0006-restore-ijk-http-event-hooks.patch @@ -0,0 +1,744 @@ +From 0dbc6cebcbfbed31c380989677ea4f84b08d9d58 Mon Sep 17 00:00:00 2001 +From: qianlongxu +Date: Tue, 23 Jan 2024 15:25:48 +0800 +Subject: [PATCH 06/15] restore ijk http event hooks + +--- + libavformat/http.c | 35 ++++++- + libavformat/tcp.c | 60 ++++++++++- + libavutil/Makefile | 2 + + libavutil/application.c | 215 ++++++++++++++++++++++++++++++++++++++++ + libavutil/application.h | 122 +++++++++++++++++++++++ + libavutil/dict.c | 39 +++++++- + libavutil/dict.h | 10 ++ + libavutil/error.h | 4 + + 8 files changed, 479 insertions(+), 8 deletions(-) + create mode 100644 libavutil/application.c + create mode 100644 libavutil/application.h + +diff --git a/libavformat/http.c b/libavformat/http.c +index c5c48c7..31c07a4 100644 +--- a/libavformat/http.c ++++ b/libavformat/http.c +@@ -33,6 +33,7 @@ + #include "libavutil/opt.h" + #include "libavutil/time.h" + #include "libavutil/parseutils.h" ++#include "libavutil/application.h" + + #include "avformat.h" + #include "http.h" +@@ -136,6 +137,9 @@ typedef struct HTTPContext { + char *new_location; + AVDictionary *redirect_cache; + uint64_t filesize_from_content_range; ++ char *tcp_hook; ++ char *app_ctx_intptr; ++ AVApplicationContext *app_ctx; + } HTTPContext; + + #define OFFSET(x) offsetof(HTTPContext, x) +@@ -178,6 +182,8 @@ static const AVOption options[] = { + { "resource", "The resource requested by a client", OFFSET(resource), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E }, + { "reply_code", "The http status code to return to a client", OFFSET(reply_code), AV_OPT_TYPE_INT, { .i64 = 200}, INT_MIN, 599, E}, + { "short_seek_size", "Threshold to favor readahead over seek.", OFFSET(short_seek_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D }, ++ { "http-tcp-hook", "hook protocol on tcp", OFFSET(tcp_hook), AV_OPT_TYPE_STRING, { .str = "tcp" }, 0, 0, D | E }, ++ { "ijkapplication", "AVApplicationContext", OFFSET(app_ctx_intptr), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, .flags = D }, + { NULL } + }; + +@@ -208,6 +214,7 @@ static int http_open_cnx_internal(URLContext *h, AVDictionary **options) + char buf[1024], urlbuf[MAX_URL_SIZE]; + int port, use_proxy, err = 0; + HTTPContext *s = h->priv_data; ++ lower_proto = s->tcp_hook; + + av_url_split(proto, sizeof(proto), auth, sizeof(auth), + hostname, sizeof(hostname), &port, +@@ -263,6 +270,13 @@ static int http_open_cnx_internal(URLContext *h, AVDictionary **options) + ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL); + + if (!s->hd) { ++ av_dict_set_intptr(options, "ijkapplication", (uintptr_t)s->app_ctx, 0); ++ ++ // AVDictionaryEntry *t = NULL; ++ // while ((t = av_dict_get(*options, "", t, AV_DICT_IGNORE_SUFFIX))) { ++ // av_log(NULL, AV_LOG_INFO, "%-*s: %-*s = %s\n", 12, "http open tcp", 28, t->key, t->value); ++ // } ++ + err = ffurl_open_whitelist(&s->hd, buf, AVIO_FLAG_READ_WRITE, + &h->interrupt_callback, options, + h->protocol_whitelist, h->protocol_blacklist, h); +@@ -683,6 +697,10 @@ static int http_open(URLContext *h, const char *uri, int flags, + { + HTTPContext *s = h->priv_data; + int ret; ++ s->app_ctx = (AVApplicationContext *)av_dict_strtoptr(s->app_ctx_intptr); ++ ++ // av_log(NULL, AV_LOG_INFO, "%-*s: %-*s = %s\n", 12, "xql http_open verify", 28, "ijkapplication", s->app_ctx_intptr); ++ // av_log(NULL, AV_LOG_INFO, "%-*s: %-*s = %s\n", 12, "xql http_open verify", 28, "tcp_hook", s->tcp_hook); + + if( s->seekable == 1 ) + h->is_streamed = 0; +@@ -719,7 +737,9 @@ static int http_open(URLContext *h, const char *uri, int flags, + if (s->listen) { + return http_listen(h, uri, flags, options); + } ++ av_application_will_http_open(s->app_ctx, (void*)h, uri); + ret = http_open_cnx(h, options); ++ av_application_did_http_open(s->app_ctx, (void*)h, uri, ret, s->http_code, s->filesize); + bail_out: + if (ret < 0) { + av_dict_free(&s->chained_options); +@@ -1610,7 +1630,14 @@ static int http_buf_read(URLContext *h, uint8_t *buf, int size) + uint64_t target_end = s->end_off ? s->end_off : s->filesize; + if ((!s->willclose || s->chunksize == UINT64_MAX) && s->off >= target_end) + return AVERROR_EOF; +- len = ffurl_read(s->hd, buf, size); ++ len = size; ++ if (s->filesize > 0 && s->filesize != UINT64_MAX && s->filesize != INT32_MAX) { ++ int64_t unread = s->filesize - s->off; ++ if (len > unread) ++ len = (int)unread; ++ } ++ if (len > 0) ++ len = ffurl_read(s->hd, buf, len); + if ((!len || len == AVERROR_EOF) && + (!s->willclose || s->chunksize == UINT64_MAX) && s->off < target_end) { + av_log(h, AV_LOG_ERROR, +@@ -1949,7 +1976,9 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo + s->hd = NULL; + + /* if it fails, continue on old connection */ ++ av_application_will_http_seek(s->app_ctx, (void*)h, s->location, off); + if ((ret = http_open_cnx(h, &options)) < 0) { ++ av_application_did_http_seek(s->app_ctx, (void*)h, s->location, off, ret, s->http_code); + av_dict_free(&options); + memcpy(s->buffer, old_buf, old_buf_size); + s->buf_ptr = s->buffer; +@@ -1958,6 +1987,7 @@ static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int fo + s->off = old_off; + return ret; + } ++ av_application_did_http_seek(s->app_ctx, (void*)h, s->location, off, ret, s->http_code); + av_dict_free(&options); + ffurl_close(old_hd); + return off; +@@ -2051,6 +2081,7 @@ static int http_proxy_open(URLContext *h, const char *uri, int flags) + HTTPAuthType cur_auth_type; + char *authstr; + ++ s->app_ctx = (AVApplicationContext *)av_dict_strtoptr(s->app_ctx_intptr); + if( s->seekable == 1 ) + h->is_streamed = 0; + else +@@ -2063,7 +2094,7 @@ static int http_proxy_open(URLContext *h, const char *uri, int flags) + if (*path == '/') + path++; + +- ff_url_join(lower_url, sizeof(lower_url), "tcp", NULL, hostname, port, ++ ff_url_join(lower_url, sizeof(lower_url), s->tcp_hook, NULL, hostname, port, + NULL); + redo: + ret = ffurl_open_whitelist(&s->hd, lower_url, AVIO_FLAG_READ_WRITE, +diff --git a/libavformat/tcp.c b/libavformat/tcp.c +index a11ccbb..21d18d1 100644 +--- a/libavformat/tcp.c ++++ b/libavformat/tcp.c +@@ -23,6 +23,8 @@ + #include "libavutil/parseutils.h" + #include "libavutil/opt.h" + #include "libavutil/time.h" ++#include "libavutil/avstring.h" ++#include "libavutil/application.h" + + #include "internal.h" + #include "network.h" +@@ -45,6 +47,9 @@ typedef struct TCPContext { + #if !HAVE_WINSOCK2_H + int tcp_mss; + #endif /* !HAVE_WINSOCK2_H */ ++ ++ char * app_ctx_intptr; ++ AVApplicationContext *app_ctx; + } TCPContext; + + #define OFFSET(x) offsetof(TCPContext, x) +@@ -60,6 +65,8 @@ static const AVOption options[] = { + #if !HAVE_WINSOCK2_H + { "tcp_mss", "Maximum segment size for outgoing TCP packets", OFFSET(tcp_mss), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, + #endif /* !HAVE_WINSOCK2_H */ ++ { "ijkapplication", "AVApplicationContext", OFFSET(app_ctx_intptr), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, .flags = D }, ++ { "connect_timeout", "set connect timeout (in microseconds) of socket", OFFSET(open_timeout), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, + { NULL } + }; + +@@ -110,7 +117,21 @@ static int tcp_open(URLContext *h, const char *uri, int flags) + int ret; + char hostname[1024],proto[1024],path[1024]; + char portstr[10]; +- s->open_timeout = 5000000; ++ AVAppTcpIOControl control = {0}; ++ ++ int ret2; ++ if (s->open_timeout < 0) { ++ s->open_timeout = 15000000; ++ } ++ // av_log(NULL, AV_LOG_INFO, "xql tcp_open uri %s", uri); ++ // av_log(NULL, AV_LOG_INFO, "%-*s: %-*s = %s\n", 12, "xql tcp_open verify", 28, "ijkapplication", s->app_ctx_intptr); ++ // av_log(NULL, AV_LOG_INFO, "%-*s: %-*s = %d\n", 12, "xql tcp_open verify", 28, "connect_timeout", s->open_timeout); ++ // av_log(NULL, AV_LOG_INFO, "%-*s: %-*s = %d\n", 12, "xql tcp_open verify", 28, "addrinfo_one_by_one", s->addrinfo_one_by_one); ++ // av_log(NULL, AV_LOG_INFO, "%-*s: %-*s = %d\n", 12, "xql tcp_open verify", 28, "addrinfo_timeout", s->addrinfo_timeout); ++ // av_log(NULL, AV_LOG_INFO, "%-*s: %-*s = %d\n", 12, "xql tcp_open verify", 28, "dns_cache_timeout", s->dns_cache_timeout); ++ // av_log(NULL, AV_LOG_INFO, "%-*s: %-*s = %d\n", 12, "xql tcp_open verify", 28, "dns_cache_clear", s->dns_cache_clear); ++ ++ s->app_ctx = (AVApplicationContext *)av_dict_strtoptr(s->app_ctx_intptr); + + av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), + &port, path, sizeof(path), uri); +@@ -131,6 +152,9 @@ static int tcp_open(URLContext *h, const char *uri, int flags) + } + if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) { + s->rw_timeout = strtol(buf, NULL, 10); ++ if (s->rw_timeout >= 0) { ++ s->open_timeout = s->rw_timeout; ++ } + } + if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) { + s->listen_timeout = strtol(buf, NULL, 10); +@@ -140,7 +164,7 @@ static int tcp_open(URLContext *h, const char *uri, int flags) + } + } + if (s->rw_timeout >= 0) { +- s->open_timeout = ++ //s->open_timeout = + h->rw_timeout = s->rw_timeout; + } + hints.ai_family = AF_UNSPEC; +@@ -198,9 +222,24 @@ static int tcp_open(URLContext *h, const char *uri, int flags) + // Socket descriptor already closed here. Safe to overwrite to client one. + fd = ret; + } else { +- ret = ff_connect_parallel(ai, s->open_timeout / 1000, 3, h, &fd, customize_fd, s); ++ ret = av_application_on_tcp_will_open(s->app_ctx); ++ if (ret) { ++ av_log(NULL, AV_LOG_WARNING, "terminated by application in AVAPP_CTRL_WILL_TCP_OPEN"); ++ goto fail1; ++ } ++ ret = ff_connect_parallel(cur_ai, s->open_timeout / 1000, 3, h, &fd, customize_fd, s); ++ ++ ret2 = av_application_on_tcp_did_open(s->app_ctx, ret, fd, &control); ++ + if (ret < 0) + goto fail1; ++ ++ if (ret2) { ++ av_log(NULL, AV_LOG_WARNING, "terminated by application in AVAPP_CTRL_DID_TCP_OPEN"); ++ ret = ret2; ++ goto fail1; ++ } ++ av_log(NULL, AV_LOG_INFO, "tcp did open uri = %s, ip = %s\n", uri , control.ip); + } + + h->is_streamed = 1; +@@ -241,12 +280,18 @@ static int tcp_read(URLContext *h, uint8_t *buf, int size) + + if (!(h->flags & AVIO_FLAG_NONBLOCK)) { + ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback); +- if (ret) ++ if (ret) { ++ if (ret == AVERROR(ETIMEDOUT)) { ++ ret = AVERROR_TCP_READ_TIMEOUT; ++ } + return ret; ++ } + } + ret = recv(s->fd, buf, size, 0); + if (ret == 0) + return AVERROR_EOF; ++ if (ret > 0) ++ av_application_did_io_tcp_read(s->app_ctx, (void*)h, ret); + return ret < 0 ? ff_neterrno() : ret; + } + +@@ -257,9 +302,14 @@ static int tcp_write(URLContext *h, const uint8_t *buf, int size) + + if (!(h->flags & AVIO_FLAG_NONBLOCK)) { + ret = ff_network_wait_fd_timeout(s->fd, 1, h->rw_timeout, &h->interrupt_callback); +- if (ret) ++ if (ret) { ++ if (ret == AVERROR(ETIMEDOUT)) { ++ ret = AVERROR_TCP_WRITE_TIMEOUT; ++ } + return ret; ++ } + } ++ + ret = send(s->fd, buf, size, MSG_NOSIGNAL); + return ret < 0 ? ff_neterrno() : ret; + } +diff --git a/libavutil/Makefile b/libavutil/Makefile +index 2744693..2c55e6a 100644 +--- a/libavutil/Makefile ++++ b/libavutil/Makefile +@@ -9,6 +9,7 @@ HEADERS = adler32.h \ + avassert.h \ + avstring.h \ + avutil.h \ ++ application.h \ + base64.h \ + blowfish.h \ + bprint.h \ +@@ -106,6 +107,7 @@ OBJS = adler32.o \ + audio_fifo.o \ + avstring.o \ + avsscanf.o \ ++ application.o \ + base64.o \ + blowfish.o \ + bprint.o \ +diff --git a/libavutil/application.c b/libavutil/application.c +new file mode 100644 +index 0000000..ec7c1f0 +--- /dev/null ++++ b/libavutil/application.c +@@ -0,0 +1,215 @@ ++/* ++ * copyright (c) 2016 Zhang Rui ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include "application.h" ++#include "../libavformat/network.h" ++#include "avstring.h" ++#include "mem.h" ++ ++void av_application_on_io_traffic(AVApplicationContext *h, AVAppIOTraffic *event); ++ ++int av_application_alloc(AVApplicationContext **ph, void *opaque) ++{ ++ AVApplicationContext *h = NULL; ++ ++ h = av_mallocz(sizeof(AVApplicationContext)); ++ if (!h) ++ return AVERROR(ENOMEM); ++ ++ h->opaque = opaque; ++ ++ *ph = h; ++ return 0; ++} ++ ++int av_application_open(AVApplicationContext **ph, void *opaque) ++{ ++ int ret = av_application_alloc(ph, opaque); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++void av_application_close(AVApplicationContext *h) ++{ ++ av_free(h); ++} ++ ++void av_application_closep(AVApplicationContext **ph) ++{ ++ if (!ph || !*ph) ++ return; ++ ++ av_application_close(*ph); ++ *ph = NULL; ++} ++ ++void av_application_on_http_event(AVApplicationContext *h, int event_type, AVAppHttpEvent *event) ++{ ++ if (h && h->func_on_app_event) ++ h->func_on_app_event(h, event_type, (void *)event, sizeof(AVAppHttpEvent)); ++} ++ ++void av_application_will_http_open(AVApplicationContext *h, void *obj, const char *url) ++{ ++ AVAppHttpEvent event = {0}; ++ ++ if (!h || !obj || !url) ++ return; ++ ++ event.obj = obj; ++ av_strlcpy(event.url, url, sizeof(event.url)); ++ ++ av_application_on_http_event(h, AVAPP_EVENT_WILL_HTTP_OPEN, &event); ++} ++ ++void av_application_did_http_open(AVApplicationContext *h, void *obj, const char *url, int error, int http_code, int64_t filesize) ++{ ++ AVAppHttpEvent event = {0}; ++ ++ if (!h || !obj || !url) ++ return; ++ ++ event.obj = obj; ++ av_strlcpy(event.url, url, sizeof(event.url)); ++ event.error = error; ++ event.http_code = http_code; ++ event.filesize = filesize; ++ ++ av_application_on_http_event(h, AVAPP_EVENT_DID_HTTP_OPEN, &event); ++} ++ ++void av_application_will_http_seek(AVApplicationContext *h, void *obj, const char *url, int64_t offset) ++{ ++ AVAppHttpEvent event = {0}; ++ ++ if (!h || !obj || !url) ++ return; ++ ++ event.obj = obj; ++ event.offset = offset; ++ av_strlcpy(event.url, url, sizeof(event.url)); ++ ++ av_application_on_http_event(h, AVAPP_EVENT_WILL_HTTP_SEEK, &event); ++} ++ ++void av_application_did_http_seek(AVApplicationContext *h, void *obj, const char *url, int64_t offset, int error, int http_code) ++{ ++ AVAppHttpEvent event = {0}; ++ ++ if (!h || !obj || !url) ++ return; ++ ++ event.obj = obj; ++ event.offset = offset; ++ av_strlcpy(event.url, url, sizeof(event.url)); ++ event.error = error; ++ event.http_code = http_code; ++ ++ av_application_on_http_event(h, AVAPP_EVENT_DID_HTTP_SEEK, &event); ++} ++ ++void av_application_on_io_traffic(AVApplicationContext *h, AVAppIOTraffic *event) ++{ ++ if (h && h->func_on_app_event) ++ h->func_on_app_event(h, AVAPP_EVENT_IO_TRAFFIC, (void *)event, sizeof(AVAppIOTraffic)); ++} ++ ++int av_application_on_io_control(AVApplicationContext *h, int event_type, AVAppIOControl *control) ++{ ++ if (h && h->func_on_app_event) ++ return h->func_on_app_event(h, event_type, (void *)control, sizeof(AVAppIOControl)); ++ return 0; ++} ++ ++int av_application_on_tcp_will_open(AVApplicationContext *h) ++{ ++ if (h && h->func_on_app_event) { ++ AVAppTcpIOControl control = {0}; ++ return h->func_on_app_event(h, AVAPP_CTRL_WILL_TCP_OPEN, (void *)&control, sizeof(AVAppTcpIOControl)); ++ } ++ return 0; ++} ++ ++// only callback returns error ++int av_application_on_tcp_did_open(AVApplicationContext *h, int error, int fd, AVAppTcpIOControl *control) ++{ ++ struct sockaddr_storage so_stg; ++ int ret = 0; ++ socklen_t so_len = sizeof(so_stg); ++ int so_family; ++ char *so_ip_name = control->ip; ++ ++ if (!h || !h->func_on_app_event || fd <= 0) ++ return 0; ++ ++ ret = getpeername(fd, (struct sockaddr *)&so_stg, &so_len); ++ if (ret) ++ return 0; ++ control->error = error; ++ control->fd = fd; ++ ++ so_family = ((struct sockaddr*)&so_stg)->sa_family; ++ switch (so_family) { ++ case AF_INET: { ++ struct sockaddr_in* in4 = (struct sockaddr_in*)&so_stg; ++ if (inet_ntop(AF_INET, &(in4->sin_addr), so_ip_name, sizeof(control->ip))) { ++ control->family = AF_INET; ++ control->port = in4->sin_port; ++ } ++ break; ++ } ++ case AF_INET6: { ++ struct sockaddr_in6* in6 = (struct sockaddr_in6*)&so_stg; ++ if (inet_ntop(AF_INET6, &(in6->sin6_addr), so_ip_name, sizeof(control->ip))) { ++ control->family = AF_INET6; ++ control->port = in6->sin6_port; ++ } ++ break; ++ } ++ } ++ ++ return h->func_on_app_event(h, AVAPP_CTRL_DID_TCP_OPEN, (void *)control, sizeof(AVAppTcpIOControl)); ++} ++ ++void av_application_on_async_statistic(AVApplicationContext *h, AVAppAsyncStatistic *statistic) ++{ ++ if (h && h->func_on_app_event) ++ h->func_on_app_event(h, AVAPP_EVENT_ASYNC_STATISTIC, (void *)statistic, sizeof(AVAppAsyncStatistic)); ++} ++ ++void av_application_on_async_read_speed(AVApplicationContext *h, AVAppAsyncReadSpeed *speed) ++{ ++ if (h && h->func_on_app_event) ++ h->func_on_app_event(h, AVAPP_EVENT_ASYNC_READ_SPEED, (void *)speed, sizeof(AVAppAsyncReadSpeed)); ++} ++ ++void av_application_did_io_tcp_read(AVApplicationContext *h, void *obj, int bytes) ++{ ++ AVAppIOTraffic event = {0}; ++ if (!h || !obj || bytes <= 0) ++ return; ++ ++ event.obj = obj; ++ event.bytes = bytes; ++ ++ av_application_on_io_traffic(h, &event); ++} +diff --git a/libavutil/application.h b/libavutil/application.h +new file mode 100644 +index 0000000..b64cb39 +--- /dev/null ++++ b/libavutil/application.h +@@ -0,0 +1,122 @@ ++/* ++ * copyright (c) 2016 Zhang Rui ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#ifndef AVUTIL_APPLICATION_H ++#define AVUTIL_APPLICATION_H ++ ++#include ++#include "libavutil/log.h" ++ ++#define AVAPP_EVENT_WILL_HTTP_OPEN 1 //AVAppHttpEvent ++#define AVAPP_EVENT_DID_HTTP_OPEN 2 //AVAppHttpEvent ++#define AVAPP_EVENT_WILL_HTTP_SEEK 3 //AVAppHttpEvent ++#define AVAPP_EVENT_DID_HTTP_SEEK 4 //AVAppHttpEvent ++ ++#define AVAPP_EVENT_ASYNC_STATISTIC 0x11000 //AVAppAsyncStatistic ++#define AVAPP_EVENT_ASYNC_READ_SPEED 0x11001 //AVAppAsyncReadSpeed ++#define AVAPP_EVENT_IO_TRAFFIC 0x12204 //AVAppIOTraffic ++ ++#define AVAPP_CTRL_WILL_TCP_OPEN 0x20001 //AVAppTcpIOControl ++#define AVAPP_CTRL_DID_TCP_OPEN 0x20002 //AVAppTcpIOControl ++ ++#define AVAPP_CTRL_WILL_HTTP_OPEN 0x20003 //AVAppIOControl ++#define AVAPP_CTRL_WILL_LIVE_OPEN 0x20005 //AVAppIOControl ++ ++#define AVAPP_CTRL_WILL_CONCAT_SEGMENT_OPEN 0x20007 //AVAppIOControl ++ ++typedef struct AVAppIOControl { ++ size_t size; ++ char url[4096]; /* in, out */ ++ int segment_index; /* in, default = 0 */ ++ int retry_counter; /* in */ ++ ++ int is_handled; /* out, default = false */ ++ int is_url_changed; /* out, default = false */ ++} AVAppIOControl; ++ ++typedef struct AVAppTcpIOControl { ++ int error; ++ int family; ++ char ip[96]; ++ int port; ++ int fd; ++} AVAppTcpIOControl; ++ ++typedef struct AVAppAsyncStatistic { ++ size_t size; ++ int64_t buf_backwards; ++ int64_t buf_forwards; ++ int64_t buf_capacity; ++} AVAppAsyncStatistic; ++ ++typedef struct AVAppAsyncReadSpeed { ++ size_t size; ++ int is_full_speed; ++ int64_t io_bytes; ++ int64_t elapsed_milli; ++} AVAppAsyncReadSpeed; ++ ++typedef struct AVAppHttpEvent ++{ ++ void *obj; ++ char url[4096]; ++ int64_t offset; ++ int error; ++ int http_code; ++ int64_t filesize; ++} AVAppHttpEvent; ++ ++typedef struct AVAppIOTraffic ++{ ++ void *obj; ++ int bytes; ++} AVAppIOTraffic; ++ ++typedef struct AVApplicationContext AVApplicationContext; ++struct AVApplicationContext { ++ const AVClass *av_class; /**< information for av_log(). Set by av_application_open(). */ ++ void *opaque; /**< user data. */ ++ ++ int (*func_on_app_event)(AVApplicationContext *h, int event_type ,void *obj, size_t size); ++}; ++ ++int av_application_alloc(AVApplicationContext **ph, void *opaque); ++int av_application_open(AVApplicationContext **ph, void *opaque); ++void av_application_close(AVApplicationContext *h); ++void av_application_closep(AVApplicationContext **ph); ++ ++void av_application_on_http_event(AVApplicationContext *h, int event_type, AVAppHttpEvent *event); ++void av_application_will_http_open(AVApplicationContext *h, void *obj, const char *url); ++void av_application_did_http_open(AVApplicationContext *h, void *obj, const char *url, int error, int http_code, int64_t filesize); ++void av_application_will_http_seek(AVApplicationContext *h, void *obj, const char *url, int64_t offset); ++void av_application_did_http_seek(AVApplicationContext *h, void *obj, const char *url, int64_t offset, int error, int http_code); ++ ++void av_application_did_io_tcp_read(AVApplicationContext *h, void *obj, int bytes); ++ ++int av_application_on_io_control(AVApplicationContext *h, int event_type, AVAppIOControl *control); ++ ++int av_application_on_tcp_will_open(AVApplicationContext *h); ++int av_application_on_tcp_did_open(AVApplicationContext *h, int error, int fd, AVAppTcpIOControl *control); ++ ++void av_application_on_async_statistic(AVApplicationContext *h, AVAppAsyncStatistic *statistic); ++void av_application_on_async_read_speed(AVApplicationContext *h, AVAppAsyncReadSpeed *speed); ++ ++ ++#endif /* AVUTIL_APPLICATION_H */ +diff --git a/libavutil/dict.c b/libavutil/dict.c +index 9d3d96c..9ec8dfa 100644 +--- a/libavutil/dict.c ++++ b/libavutil/dict.c +@@ -42,7 +42,7 @@ AVDictionaryEntry *av_dict_get(const AVDictionary *m, const char *key, + { + unsigned int i, j; + +- if (!m) ++ if (!m || !key) + return NULL; + + if (prev) +@@ -153,6 +153,43 @@ int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, + return av_dict_set(pm, key, valuestr, flags); + } + ++int av_dict_set_intptr(AVDictionary **pm, const char *key, uintptr_t value, ++ int flags) ++{ ++ char valuestr[22]; ++ snprintf(valuestr, sizeof(valuestr), "%p", value); ++ flags &= ~AV_DICT_DONT_STRDUP_VAL; ++ return av_dict_set(pm, key, valuestr, flags); ++} ++ ++uintptr_t av_dict_get_intptr(const AVDictionary *m, const char* key) { ++ uintptr_t ptr = NULL; ++ AVDictionaryEntry *t = NULL; ++ if ((t = av_dict_get(m, key, NULL, 0))) { ++ return av_dict_strtoptr(t->value); ++ } ++ return NULL; ++} ++ ++uintptr_t av_dict_strtoptr(char * value) { ++ uintptr_t ptr = NULL; ++ char *next = NULL; ++ if(!value || value[0] !='0' || (value[1]|0x20)!='x') { ++ return NULL; ++ } ++ ptr = strtoull(value, &next, 16); ++ if (next == value) { ++ return NULL; ++ } ++ return ptr; ++} ++ ++char * av_dict_ptrtostr(uintptr_t value) { ++ char valuestr[22] = {0}; ++ snprintf(valuestr, sizeof(valuestr), "%p", value); ++ return av_strdup(valuestr); ++} ++ + static int parse_key_value_pair(AVDictionary **pm, const char **buf, + const char *key_val_sep, const char *pairs_sep, + int flags) +diff --git a/libavutil/dict.h b/libavutil/dict.h +index 0d1afc6..d200b42 100644 +--- a/libavutil/dict.h ++++ b/libavutil/dict.h +@@ -135,6 +135,16 @@ int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags + */ + int av_dict_set_int(AVDictionary **pm, const char *key, int64_t value, int flags); + ++/** ++ * Convenience wrapper for av_dict_get that converts the value to a pointer ++ * and stores it. ++ * ++ */ ++int av_dict_set_intptr(AVDictionary **pm, const char *key, uintptr_t value, int flags); ++uintptr_t av_dict_get_intptr(const AVDictionary *m, const char* key); ++uintptr_t av_dict_strtoptr(char * value); ++char * av_dict_ptrtostr(uintptr_t value); ++ + /** + * Parse the key/value pairs list and add the parsed entries to a dictionary. + * +diff --git a/libavutil/error.h b/libavutil/error.h +index 0d3269a..2943350 100644 +--- a/libavutil/error.h ++++ b/libavutil/error.h +@@ -84,6 +84,10 @@ + + #define AV_ERROR_MAX_STRING_SIZE 64 + ++#define AVERROR_TCP_CONNECT_TIMEOUT -1001 ++#define AVERROR_TCP_READ_TIMEOUT -1002 ++#define AVERROR_TCP_WRITE_TIMEOUT -1003 ++ + /** + * Put a description of the AVERROR code errnum in errbuf. + * In case of failure the global variable errno is set to indicate the +-- +2.39.3 (Apple Git-145) + diff --git a/shell/patches/ffmpeg-release-5.1/0007-restore-ijk-dns-cache.patch b/shell/patches/ffmpeg-release-5.1/0007-restore-ijk-dns-cache.patch new file mode 100644 index 0000000000..ab494075d0 --- /dev/null +++ b/shell/patches/ffmpeg-release-5.1/0007-restore-ijk-dns-cache.patch @@ -0,0 +1,813 @@ +From 5ce4f79fc1860da59233d6a1b6ff7e563d244466 Mon Sep 17 00:00:00 2001 +From: qianlongxu +Date: Tue, 23 Jan 2024 15:45:28 +0800 +Subject: [PATCH 07/15] restore ijk dns cache + +--- + libavformat/hls.c | 46 +++++- + libavformat/tcp.c | 322 ++++++++++++++++++++++++++++++++++++++++-- + libavutil/Makefile | 2 + + libavutil/dns_cache.c | 231 ++++++++++++++++++++++++++++++ + libavutil/dns_cache.h | 39 +++++ + 5 files changed, 623 insertions(+), 17 deletions(-) + create mode 100644 libavutil/dns_cache.c + create mode 100644 libavutil/dns_cache.h + +diff --git a/libavformat/hls.c b/libavformat/hls.c +index 2e3bff6..35dd88c 100644 +--- a/libavformat/hls.c ++++ b/libavformat/hls.c +@@ -222,6 +222,7 @@ typedef struct HLSContext { + AVIOInterruptCB *interrupt_callback; + AVDictionary *avio_opts; + AVDictionary *seg_format_opts; ++ char *seg_inherit_opts; + char *allowed_extensions; + int max_reload; + int http_persistent; +@@ -1935,7 +1936,32 @@ static int hls_close(AVFormatContext *s) + return 0; + } + +-static int hls_read_header(AVFormatContext *s) ++static int copy_hls_headers_for_http(AVDictionary **dst, const AVDictionary *src, const char *opts) ++{ ++ if (!opts) ++ return 0; ++ ++ char *my_opts = opts; ++ char *saved = NULL; ++ char *opt = NULL; ++ int ret = 0; ++ ++ while ((opt = av_strtok(my_opts, ",", &saved))) { ++ AVDictionaryEntry *t = NULL; ++ while ((t = av_dict_get(src, "", t, AV_DICT_IGNORE_SUFFIX))) { ++ if (t->key && !strcmp(t->key, opt)) { ++ ret = av_dict_set(dst, t->key, t->value, 0); ++ if (ret < 0) ++ return ret; ++ } ++ } ++ my_opts = saved; ++ } ++ ++ return ret; ++} ++ ++static int hls_read_header2(AVFormatContext *s, AVDictionary **a_options) + { + HLSContext *c = s->priv_data; + int ret = 0, i; +@@ -1948,9 +1974,20 @@ static int hls_read_header(AVFormatContext *s) + c->first_timestamp = AV_NOPTS_VALUE; + c->cur_timestamp = AV_NOPTS_VALUE; + ++ //pb only include keys which in hls_options list. + if ((ret = ffio_copy_url_options(s->pb, &c->avio_opts)) < 0) + return ret; + ++ //current a_options is original options,you can filter special keys ++ copy_hls_headers_for_http(&c->avio_opts, *a_options, c->seg_inherit_opts); ++ //use segment format options override inherit options. ++ av_dict_copy(&c->avio_opts, c->seg_format_opts, 0); ++ ++ // AVDictionaryEntry *t = NULL; ++ // while ((t = av_dict_get(c->avio_opts, "", t, AV_DICT_IGNORE_SUFFIX))) { ++ // av_log(NULL, AV_LOG_INFO, "%-*s: %-*s = %s\n", 12, "hls_read_header2", 28, t->key, t->value); ++ // } ++ + /* XXX: Some HLS servers don't like being sent the range header, + in this case, need to setting http_seekable = 0 to disable + the range header */ +@@ -2047,6 +2084,7 @@ static int hls_read_header(AVFormatContext *s) + pls->needed = 1; + pls->parent = s; + ++ av_dict_copy(&options, c->avio_opts, 0); + /* + * If this is a live stream and this playlist looks like it is one segment + * behind, try to sync it up so that every substream starts at the same +@@ -2155,8 +2193,6 @@ static int hls_read_header(AVFormatContext *s) + if ((ret = ff_copy_whiteblacklists(pls->ctx, s)) < 0) + return ret; + +- av_dict_copy(&options, c->seg_format_opts, 0); +- + ret = avformat_open_input(&pls->ctx, pls->segments[0]->url, in_fmt, &options); + av_dict_free(&options); + if (ret < 0) +@@ -2595,6 +2631,8 @@ static const AVOption hls_options[] = { + OFFSET(http_seekable), AV_OPT_TYPE_BOOL, { .i64 = -1}, -1, 1, FLAGS}, + {"seg_format_options", "Set options for segment demuxer", + OFFSET(seg_format_opts), AV_OPT_TYPE_DICT, {.str = NULL}, 0, 0, FLAGS}, ++ {"seg_inherit_options", "Special keys inherit form options,apply for segment demuxer", ++ OFFSET(seg_inherit_opts), AV_OPT_TYPE_STRING, {.str = NULL}, INT_MIN, INT_MAX, FLAGS}, + {NULL} + }; + +@@ -2613,7 +2651,7 @@ const AVInputFormat ff_hls_demuxer = { + .flags = AVFMT_NOGENSEARCH | AVFMT_TS_DISCONT | AVFMT_NO_BYTE_SEEK, + .flags_internal = FF_FMT_INIT_CLEANUP, + .read_probe = hls_probe, +- .read_header = hls_read_header, ++ .read_header2 = hls_read_header2, + .read_packet = hls_read_packet, + .read_close = hls_close, + .read_seek = hls_read_seek, +diff --git a/libavformat/tcp.c b/libavformat/tcp.c +index 21d18d1..1a85363 100644 +--- a/libavformat/tcp.c ++++ b/libavformat/tcp.c +@@ -25,6 +25,7 @@ + #include "libavutil/time.h" + #include "libavutil/avstring.h" + #include "libavutil/application.h" ++#include "libavutil/dns_cache.h" + + #include "internal.h" + #include "network.h" +@@ -33,6 +34,9 @@ + #if HAVE_POLL_H + #include + #endif ++#if HAVE_PTHREADS ++#include ++#endif + + typedef struct TCPContext { + const AVClass *class; +@@ -49,6 +53,10 @@ typedef struct TCPContext { + #endif /* !HAVE_WINSOCK2_H */ + + char * app_ctx_intptr; ++ int addrinfo_one_by_one; ++ int addrinfo_timeout; ++ int64_t dns_cache_timeout; ++ int dns_cache_clear; + AVApplicationContext *app_ctx; + } TCPContext; + +@@ -67,9 +75,261 @@ static const AVOption options[] = { + #endif /* !HAVE_WINSOCK2_H */ + { "ijkapplication", "AVApplicationContext", OFFSET(app_ctx_intptr), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, .flags = D }, + { "connect_timeout", "set connect timeout (in microseconds) of socket", OFFSET(open_timeout), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, ++ { "addrinfo_one_by_one", "parse addrinfo one by one in getaddrinfo()", OFFSET(addrinfo_one_by_one), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = D|E }, ++ { "addrinfo_timeout", "set timeout (in microseconds) for getaddrinfo()", OFFSET(addrinfo_timeout), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, ++ { "dns_cache_timeout", "dns cache TTL (in microseconds)", OFFSET(dns_cache_timeout), AV_OPT_TYPE_INT, { .i64 = 0 }, -1, INT64_MAX, .flags = D|E }, ++ { "dns_cache_clear", "clear dns cache", OFFSET(dns_cache_clear), AV_OPT_TYPE_INT, { .i64 = 0}, -1, INT_MAX, .flags = D|E }, + { NULL } + }; + ++int ijk_tcp_getaddrinfo_nonblock(const char *hostname, const char *servname, ++ const struct addrinfo *hints, struct addrinfo **res, ++ int64_t timeout, ++ const AVIOInterruptCB *int_cb, int one_by_one); ++#ifdef HAVE_PTHREADS ++ ++typedef struct TCPAddrinfoRequest ++{ ++ AVBufferRef *buffer; ++ ++ pthread_mutex_t mutex; ++ pthread_cond_t cond; ++ ++ AVIOInterruptCB interrupt_callback; ++ ++ char *hostname; ++ char *servname; ++ struct addrinfo hints; ++ struct addrinfo *res; ++ ++ volatile int finished; ++ int last_error; ++} TCPAddrinfoRequest; ++ ++static void tcp_getaddrinfo_request_free(TCPAddrinfoRequest *req) ++{ ++ av_assert0(req); ++ if (req->res) { ++ freeaddrinfo(req->res); ++ req->res = NULL; ++ } ++ ++ av_freep(&req->servname); ++ av_freep(&req->hostname); ++ pthread_cond_destroy(&req->cond); ++ pthread_mutex_destroy(&req->mutex); ++ av_freep(&req); ++} ++ ++static void tcp_getaddrinfo_request_free_buffer(void *opaque, uint8_t *data) ++{ ++ av_assert0(opaque); ++ TCPAddrinfoRequest *req = (TCPAddrinfoRequest *)opaque; ++ tcp_getaddrinfo_request_free(req); ++} ++ ++static int tcp_getaddrinfo_request_create(TCPAddrinfoRequest **request, ++ const char *hostname, ++ const char *servname, ++ const struct addrinfo *hints, ++ const AVIOInterruptCB *int_cb) ++{ ++ TCPAddrinfoRequest *req = (TCPAddrinfoRequest *) av_mallocz(sizeof(TCPAddrinfoRequest)); ++ if (!req) ++ return AVERROR(ENOMEM); ++ ++ if (pthread_mutex_init(&req->mutex, NULL)) { ++ av_freep(&req); ++ return AVERROR(ENOMEM); ++ } ++ ++ if (pthread_cond_init(&req->cond, NULL)) { ++ pthread_mutex_destroy(&req->mutex); ++ av_freep(&req); ++ return AVERROR(ENOMEM); ++ } ++ ++ if (int_cb) ++ req->interrupt_callback = *int_cb; ++ ++ if (hostname) { ++ req->hostname = av_strdup(hostname); ++ if (!req->hostname) ++ goto fail; ++ } ++ ++ if (servname) { ++ req->servname = av_strdup(servname); ++ if (!req->hostname) ++ goto fail; ++ } ++ ++ if (hints) { ++ req->hints.ai_family = hints->ai_family; ++ req->hints.ai_socktype = hints->ai_socktype; ++ req->hints.ai_protocol = hints->ai_protocol; ++ req->hints.ai_flags = hints->ai_flags; ++ } ++ ++ req->buffer = av_buffer_create(NULL, 0, tcp_getaddrinfo_request_free_buffer, req, 0); ++ if (!req->buffer) ++ goto fail; ++ ++ *request = req; ++ return 0; ++fail: ++ tcp_getaddrinfo_request_free(req); ++ return AVERROR(ENOMEM); ++} ++ ++static void *tcp_getaddrinfo_worker(void *arg) ++{ ++ TCPAddrinfoRequest *req = arg; ++ ++ getaddrinfo(req->hostname, req->servname, &req->hints, &req->res); ++ pthread_mutex_lock(&req->mutex); ++ req->finished = 1; ++ pthread_cond_signal(&req->cond); ++ pthread_mutex_unlock(&req->mutex); ++ av_buffer_unref(&req->buffer); ++ return NULL; ++} ++ ++static void *tcp_getaddrinfo_one_by_one_worker(void *arg) ++{ ++ struct addrinfo *temp_addrinfo = NULL; ++ struct addrinfo *cur = NULL; ++ int ret = EAI_FAIL; ++ int i = 0; ++ int option_length = 0; ++ ++ TCPAddrinfoRequest *req = (TCPAddrinfoRequest *)arg; ++ ++ int family_option[2] = {AF_INET, AF_INET6}; ++ ++ option_length = sizeof(family_option) / sizeof(family_option[0]); ++ ++ for (; i < option_length; ++i) { ++ struct addrinfo *hint = &req->hints; ++ hint->ai_family = family_option[i]; ++ ret = getaddrinfo(req->hostname, req->servname, hint, &temp_addrinfo); ++ if (ret) { ++ req->last_error = ret; ++ continue; ++ } ++ pthread_mutex_lock(&req->mutex); ++ if (!req->res) { ++ req->res = temp_addrinfo; ++ } else { ++ cur = req->res; ++ while (cur->ai_next) ++ cur = cur->ai_next; ++ cur->ai_next = temp_addrinfo; ++ } ++ pthread_mutex_unlock(&req->mutex); ++ } ++ pthread_mutex_lock(&req->mutex); ++ req->finished = 1; ++ pthread_cond_signal(&req->cond); ++ pthread_mutex_unlock(&req->mutex); ++ av_buffer_unref(&req->buffer); ++ return NULL; ++} ++ ++int ijk_tcp_getaddrinfo_nonblock(const char *hostname, const char *servname, ++ const struct addrinfo *hints, struct addrinfo **res, ++ int64_t timeout, ++ const AVIOInterruptCB *int_cb, int one_by_one) ++{ ++ int ret; ++ int64_t start; ++ int64_t now; ++ AVBufferRef *req_ref = NULL; ++ TCPAddrinfoRequest *req = NULL; ++ pthread_t work_thread; ++ ++ if (hostname && !hostname[0]) ++ hostname = NULL; ++ av_log(NULL, AV_LOG_INFO, "dns getaddrinfo uri = %s\n", hostname); ++ if (timeout <= 0) ++ return getaddrinfo(hostname, servname, hints, res); ++av_log(NULL, AV_LOG_INFO, "dns tcp_getaddrinfo_request_create uri = %s\n", hostname); ++ ret = tcp_getaddrinfo_request_create(&req, hostname, servname, hints, int_cb); ++ if (ret) ++ goto fail; ++ ++ req_ref = av_buffer_ref(req->buffer); ++ if (req_ref == NULL) { ++ ret = AVERROR(ENOMEM); ++ goto fail; ++ } ++ ++ /* FIXME: using a thread pool would be better. */ ++ if (one_by_one) ++ ret = pthread_create(&work_thread, NULL, tcp_getaddrinfo_one_by_one_worker, req); ++ else ++ ret = pthread_create(&work_thread, NULL, tcp_getaddrinfo_worker, req); ++ ++ if (ret) { ++ ret = AVERROR(ret); ++ goto fail; ++ } ++ ++ pthread_detach(work_thread); ++ ++ start = av_gettime(); ++ now = start; ++ ++ pthread_mutex_lock(&req->mutex); ++ while (1) { ++ int64_t wait_time = now + 100000; ++ struct timespec tv = { .tv_sec = wait_time / 1000000, ++ .tv_nsec = (wait_time % 1000000) * 1000 }; ++ ++ if (req->finished || (start + timeout < now)) { ++ if (req->res) { ++ ret = 0; ++ *res = req->res; ++ req->res = NULL; ++ } else { ++ ret = req->last_error ? req->last_error : AVERROR_EXIT; ++ } ++ break; ++ } ++#if defined(__ANDROID__) && defined(HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC) ++ ret = pthread_cond_timedwait_monotonic_np(&req->cond, &req->mutex, &tv); ++#else ++ ret = pthread_cond_timedwait(&req->cond, &req->mutex, &tv); ++#endif ++ if (ret != 0 && ret != ETIMEDOUT) { ++ av_log(NULL, AV_LOG_ERROR, "pthread_cond_timedwait failed: %d\n", ret); ++ ret = AVERROR_EXIT; ++ break; ++ } ++ ++ if (ff_check_interrupt(&req->interrupt_callback)) { ++ ret = AVERROR_EXIT; ++ break; ++ } ++ ++ now = av_gettime(); ++ } ++ pthread_mutex_unlock(&req->mutex); ++fail: ++ av_buffer_unref(&req_ref); ++ return ret; ++} ++ ++#else ++int ijk_tcp_getaddrinfo_nonblock(const char *hostname, const char *servname, ++ const struct addrinfo *hints, struct addrinfo **res, ++ int64_t timeout, ++ const AVIOInterruptCB *int_cb) ++{ ++ return getaddrinfo(hostname, servname, hints, res); ++} ++#endif ++ + static const AVClass tcp_class = { + .class_name = "tcp", + .item_name = av_default_item_name, +@@ -118,7 +378,7 @@ static int tcp_open(URLContext *h, const char *uri, int flags) + char hostname[1024],proto[1024],path[1024]; + char portstr[10]; + AVAppTcpIOControl control = {0}; +- ++ DnsCacheEntry *dns_entry = NULL; + int ret2; + if (s->open_timeout < 0) { + s->open_timeout = 15000000; +@@ -172,18 +432,37 @@ static int tcp_open(URLContext *h, const char *uri, int flags) + snprintf(portstr, sizeof(portstr), "%d", port); + if (s->listen) + hints.ai_flags |= AI_PASSIVE; +- if (!hostname[0]) +- ret = getaddrinfo(NULL, portstr, &hints, &ai); +- else +- ret = getaddrinfo(hostname, portstr, &hints, &ai); +- if (ret) { +- av_log(h, AV_LOG_ERROR, +- "Failed to resolve hostname %s: %s\n", +- hostname, gai_strerror(ret)); +- return AVERROR(EIO); ++ if (s->dns_cache_timeout > 0) { ++ if (s->dns_cache_clear) { ++ remove_dns_cache_entry(uri); ++ } else { ++ dns_entry = get_dns_cache_reference(uri); ++ } + } + +- cur_ai = ai; ++ if (!dns_entry) { ++#ifdef HAVE_PTHREADS ++ ret = ijk_tcp_getaddrinfo_nonblock(hostname, portstr, &hints, &ai, s->addrinfo_timeout, &h->interrupt_callback, s->addrinfo_one_by_one); ++#else ++ if (s->addrinfo_timeout > 0) ++ av_log(h, AV_LOG_WARNING, "Ignore addrinfo_timeout without pthreads support.\n"); ++ if (!hostname[0]) ++ ret = getaddrinfo(NULL, portstr, &hints, &ai); ++ else ++ ret = getaddrinfo(hostname, portstr, &hints, &ai); ++#endif ++ if (ret) { ++ av_log(h, AV_LOG_ERROR, ++ "Failed to resolve hostname %s: %s\n", ++ hostname, gai_strerror(ret)); ++ return AVERROR(EIO); ++ } ++ ++ cur_ai = ai; ++ } else { ++ av_log(NULL, AV_LOG_INFO, "hit dns cache uri = %s\n", uri); ++ cur_ai = dns_entry->res; ++ } + + #if HAVE_STRUCT_SOCKADDR_IN6 + // workaround for IOS9 getaddrinfo in IPv6 only network use hardcode IPv4 address can not resolve port number. +@@ -239,19 +518,36 @@ static int tcp_open(URLContext *h, const char *uri, int flags) + ret = ret2; + goto fail1; + } ++ ++ if (!dns_entry && !strstr(uri, control.ip) && s->dns_cache_timeout > 0) { ++ add_dns_cache_entry(uri, cur_ai, s->dns_cache_timeout); ++ av_log(NULL, AV_LOG_INFO, "add dns cache uri = %s, ip = %s\n", uri , control.ip); ++ } + av_log(NULL, AV_LOG_INFO, "tcp did open uri = %s, ip = %s\n", uri , control.ip); + } + + h->is_streamed = 1; + s->fd = fd; + +- freeaddrinfo(ai); ++ if (dns_entry) { ++ release_dns_cache_reference(uri, &dns_entry); ++ } else { ++ freeaddrinfo(ai); ++ } + return 0; + + fail1: + if (fd >= 0) + closesocket(fd); +- freeaddrinfo(ai); ++ ++ if (dns_entry) { ++ av_log(NULL, AV_LOG_ERROR, "hit dns cache but connect fail uri = %s, ip = %s\n", uri , control.ip); ++ release_dns_cache_reference(uri, &dns_entry); ++ remove_dns_cache_entry(uri); ++ } else { ++ freeaddrinfo(cur_ai); ++ } ++ + return ret; + } + +diff --git a/libavutil/Makefile b/libavutil/Makefile +index 2c55e6a..873a32c 100644 +--- a/libavutil/Makefile ++++ b/libavutil/Makefile +@@ -28,6 +28,7 @@ HEADERS = adler32.h \ + display.h \ + dovi_meta.h \ + downmix_info.h \ ++ dns_cache.h \ + encryption_info.h \ + error.h \ + eval.h \ +@@ -108,6 +109,7 @@ OBJS = adler32.o \ + avstring.o \ + avsscanf.o \ + application.o \ ++ dns_cache.o \ + base64.o \ + blowfish.o \ + bprint.o \ +diff --git a/libavutil/dns_cache.c b/libavutil/dns_cache.c +new file mode 100644 +index 0000000..2976a9e +--- /dev/null ++++ b/libavutil/dns_cache.c +@@ -0,0 +1,231 @@ ++/* ++ * copyright (c) 2017 Raymond Zheng ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include "libavutil/dns_cache.h" ++#include "libavutil/time.h" ++#include "libavutil/mem.h" ++#include "libavformat/network.h" ++ ++ ++#if HAVE_PTHREADS ++#include ++#endif ++ ++typedef struct DnsCacheContext DnsCacheContext; ++typedef struct DnsCacheContext { ++ AVDictionary *dns_dictionary; ++ pthread_mutex_t dns_dictionary_mutex; ++ int initialized; ++} DnsCacheContext; ++ ++static DnsCacheContext *context = NULL; ++static pthread_once_t key_once = PTHREAD_ONCE_INIT; ++ ++static void inner_init(void) { ++ int ret = 0; ++ context = (DnsCacheContext *) av_mallocz(sizeof(DnsCacheContext)); ++ if (context) { ++ ret = pthread_mutex_init(&context->dns_dictionary_mutex, NULL); ++ if (!ret) { ++ context->initialized = 1; ++ } else { ++ av_freep(&context); ++ } ++ } ++} ++ ++static void free_private_addrinfo(struct addrinfo **p_ai) { ++ struct addrinfo *ai = *p_ai; ++ ++ if (ai) { ++ if (ai->ai_addr) { ++ av_freep(&ai->ai_addr); ++ } ++ av_freep(p_ai); ++ } ++} ++ ++static int inner_remove_dns_cache(const char *uri, DnsCacheEntry *dns_cache_entry) { ++ if (context && dns_cache_entry) { ++ if (dns_cache_entry->ref_count == 0) { ++ av_dict_set_int(&context->dns_dictionary, uri, 0, 0); ++ free_private_addrinfo(&dns_cache_entry->res); ++ av_freep(&dns_cache_entry); ++ } else { ++ dns_cache_entry->delete_flag = 1; ++ } ++ } ++ ++ return 0; ++} ++ ++static DnsCacheEntry *new_dns_cache_entry(const char *uri, struct addrinfo *cur_ai, int64_t timeout) { ++ DnsCacheEntry *new_entry = NULL; ++ int64_t cur_time = av_gettime_relative(); ++ ++ if (cur_time < 0) { ++ goto fail; ++ } ++ ++ new_entry = (DnsCacheEntry *) av_mallocz(sizeof(struct DnsCacheEntry)); ++ if (!new_entry) { ++ goto fail; ++ } ++ ++ new_entry->res = (struct addrinfo *) av_mallocz(sizeof(struct addrinfo)); ++ if (!new_entry->res) { ++ av_freep(&new_entry); ++ goto fail; ++ } ++ ++ memcpy(new_entry->res, cur_ai, sizeof(struct addrinfo)); ++ ++ new_entry->res->ai_addr = (struct sockaddr *) av_mallocz(sizeof(struct sockaddr)); ++ if (!new_entry->res->ai_addr) { ++ av_freep(&new_entry->res); ++ av_freep(&new_entry); ++ goto fail; ++ } ++ ++ memcpy(new_entry->res->ai_addr, cur_ai->ai_addr, sizeof(struct sockaddr)); ++ new_entry->res->ai_canonname = NULL; ++ new_entry->res->ai_next = NULL; ++ new_entry->ref_count = 0; ++ new_entry->delete_flag = 0; ++ new_entry->expired_time = cur_time + timeout * 1000; ++ ++ return new_entry; ++ ++fail: ++ return NULL; ++} ++ ++DnsCacheEntry *get_dns_cache_reference(const char *uri) { ++ AVDictionaryEntry *elem = NULL; ++ DnsCacheEntry *dns_cache_entry = NULL; ++ int64_t cur_time = av_gettime_relative(); ++ ++ if (cur_time < 0 || !uri || strlen(uri) == 0) { ++ return NULL; ++ } ++ ++ if (!context || !context->initialized) { ++#if HAVE_PTHREADS ++ pthread_once(&key_once, inner_init); ++#endif ++ } ++ ++ if (context && context->initialized) { ++ pthread_mutex_lock(&context->dns_dictionary_mutex); ++ elem = av_dict_get(context->dns_dictionary, uri, NULL, AV_DICT_MATCH_CASE); ++ if (elem) { ++ dns_cache_entry = (DnsCacheEntry *) (intptr_t) strtoll(elem->value, NULL, 10); ++ if (dns_cache_entry) { ++ if (dns_cache_entry->expired_time < cur_time) { ++ inner_remove_dns_cache(uri, dns_cache_entry); ++ dns_cache_entry = NULL; ++ } else { ++ dns_cache_entry->ref_count++; ++ } ++ } ++ } ++ pthread_mutex_unlock(&context->dns_dictionary_mutex); ++ } ++ ++ return dns_cache_entry; ++} ++ ++int release_dns_cache_reference(const char *uri, DnsCacheEntry **p_entry) { ++ DnsCacheEntry *entry = *p_entry; ++ ++ if (!uri || strlen(uri) == 0) { ++ return -1; ++ } ++ ++ if (context && context->initialized && entry) { ++ pthread_mutex_lock(&context->dns_dictionary_mutex); ++ entry->ref_count--; ++ if (entry->delete_flag && entry->ref_count == 0) { ++ inner_remove_dns_cache(uri, entry); ++ entry = NULL; ++ } ++ pthread_mutex_unlock(&context->dns_dictionary_mutex); ++ } ++ return 0; ++} ++ ++int remove_dns_cache_entry(const char *uri) { ++ AVDictionaryEntry *elem = NULL; ++ DnsCacheEntry *dns_cache_entry = NULL; ++ ++ if (!uri || strlen(uri) == 0) { ++ return -1; ++ } ++ ++ if (context && context->initialized) { ++ pthread_mutex_lock(&context->dns_dictionary_mutex); ++ elem = av_dict_get(context->dns_dictionary, uri, NULL, AV_DICT_MATCH_CASE); ++ if (elem) { ++ dns_cache_entry = (DnsCacheEntry *) (intptr_t) strtoll(elem->value, NULL, 10); ++ if (dns_cache_entry) { ++ inner_remove_dns_cache(uri, dns_cache_entry); ++ } ++ } ++ pthread_mutex_unlock(&context->dns_dictionary_mutex); ++ } ++ ++ return 0; ++} ++ ++int add_dns_cache_entry(const char *uri, struct addrinfo *cur_ai, int64_t timeout) { ++ DnsCacheEntry *new_entry = NULL; ++ DnsCacheEntry *old_entry = NULL; ++ AVDictionaryEntry *elem = NULL; ++ ++ if (!uri || strlen(uri) == 0 || timeout <= 0) { ++ goto fail; ++ } ++ ++ if (cur_ai == NULL || cur_ai->ai_addr == NULL) { ++ goto fail; ++ } ++ ++ if (context && context->initialized) { ++ pthread_mutex_lock(&context->dns_dictionary_mutex); ++ elem = av_dict_get(context->dns_dictionary, uri, NULL, AV_DICT_MATCH_CASE); ++ if (elem) { ++ old_entry = (DnsCacheEntry *) (intptr_t) strtoll(elem->value, NULL, 10); ++ if (old_entry) { ++ pthread_mutex_unlock(&context->dns_dictionary_mutex); ++ goto fail; ++ } ++ } ++ new_entry = new_dns_cache_entry(uri, cur_ai, timeout); ++ if (new_entry) { ++ av_dict_set_int(&context->dns_dictionary, uri, (int64_t) (intptr_t) new_entry, 0); ++ } ++ pthread_mutex_unlock(&context->dns_dictionary_mutex); ++ ++ return 0; ++ } ++ ++fail: ++ return -1; ++} +diff --git a/libavutil/dns_cache.h b/libavutil/dns_cache.h +new file mode 100644 +index 0000000..23c695e +--- /dev/null ++++ b/libavutil/dns_cache.h +@@ -0,0 +1,39 @@ ++/* ++ * copyright (c) 2017 Raymond Zheng ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#ifndef AVUTIL_DNS_CACHE_H ++#define AVUTIL_DNS_CACHE_H ++ ++#include "libavutil/log.h" ++#include ++ ++typedef struct DnsCacheEntry { ++ volatile int ref_count; ++ volatile int delete_flag; ++ int64_t expired_time; ++ struct addrinfo *res; // construct by private function, not support ai_next and ai_canonname, can only be released using free_private_addrinfo ++} DnsCacheEntry; ++ ++DnsCacheEntry *get_dns_cache_reference(const char *uri); ++int release_dns_cache_reference(const char *uri, DnsCacheEntry **p_entry); ++int remove_dns_cache_entry(const char *uri); ++int add_dns_cache_entry(const char *uri, struct addrinfo *cur_ai, int64_t timeout); ++ ++#endif /* AVUTIL_DNS_CACHE_H */ +-- +2.39.3 (Apple Git-145) + diff --git a/shell/patches/ffmpeg-release-5.1/0005-fix-ffmpeg-constructed-wrong-avcc-for-vide.patch b/shell/patches/ffmpeg-release-5.1/0008-fix-ffmpeg-constructed-wrong-avcc-for-vi.patch similarity index 94% rename from shell/patches/ffmpeg-release-5.1/0005-fix-ffmpeg-constructed-wrong-avcc-for-vide.patch rename to shell/patches/ffmpeg-release-5.1/0008-fix-ffmpeg-constructed-wrong-avcc-for-vi.patch index d7f7d53c2e..df996a1fcf 100644 --- a/shell/patches/ffmpeg-release-5.1/0005-fix-ffmpeg-constructed-wrong-avcc-for-vide.patch +++ b/shell/patches/ffmpeg-release-5.1/0008-fix-ffmpeg-constructed-wrong-avcc-for-vi.patch @@ -1,7 +1,7 @@ -From e4ed8a65f39abdbcc542069782ecfe2a8dcd23c9 Mon Sep 17 00:00:00 2001 +From 742d238b20c760356ece1e23522e740fcfd8737f Mon Sep 17 00:00:00 2001 From: qianlongxu Date: Mon, 16 May 2022 16:15:10 +0800 -Subject: [PATCH 05/15] fix ffmpeg constructed wrong avcc for +Subject: [PATCH 08/15] fix ffmpeg constructed wrong avcc for videotoolbox hwaccel. --- diff --git a/shell/patches/ffmpeg-release-5.1/0006-fix-lrcdec-read-line-bug-on-osx.patch b/shell/patches/ffmpeg-release-5.1/0009-fix-lrcdec-read-line-bug-on-osx.patch similarity index 89% rename from shell/patches/ffmpeg-release-5.1/0006-fix-lrcdec-read-line-bug-on-osx.patch rename to shell/patches/ffmpeg-release-5.1/0009-fix-lrcdec-read-line-bug-on-osx.patch index 25f08d484f..2180dcd73c 100644 --- a/shell/patches/ffmpeg-release-5.1/0006-fix-lrcdec-read-line-bug-on-osx.patch +++ b/shell/patches/ffmpeg-release-5.1/0009-fix-lrcdec-read-line-bug-on-osx.patch @@ -1,7 +1,7 @@ -From 448ffbfd154cc86e4dd5aa9f43b2efd9753847ec Mon Sep 17 00:00:00 2001 +From e9742509d17c2c08b6d6d5eca8da5fe886d5cb66 Mon Sep 17 00:00:00 2001 From: qianlongxu Date: Mon, 22 May 2023 18:20:51 +0800 -Subject: [PATCH 06/15] fix lrcdec read line bug on osx. +Subject: [PATCH 09/15] fix lrcdec read line bug on osx. --- libavformat/lrcdec.c | 20 +++++++++++--------- diff --git a/shell/patches/ffmpeg-release-5.1/0007-Correct-the-wrong-codecpar-codec_id-which-read.patch b/shell/patches/ffmpeg-release-5.1/0010-Correct-the-wrong-codecpar-codec_id-whic.patch similarity index 94% rename from shell/patches/ffmpeg-release-5.1/0007-Correct-the-wrong-codecpar-codec_id-which-read.patch rename to shell/patches/ffmpeg-release-5.1/0010-Correct-the-wrong-codecpar-codec_id-whic.patch index 1ce447344a..493e6598ba 100644 --- a/shell/patches/ffmpeg-release-5.1/0007-Correct-the-wrong-codecpar-codec_id-which-read.patch +++ b/shell/patches/ffmpeg-release-5.1/0010-Correct-the-wrong-codecpar-codec_id-whic.patch @@ -1,7 +1,7 @@ -From d067bd96497d0bc0bd91feda20b7ca2c7e651f79 Mon Sep 17 00:00:00 2001 +From de06913f5f86b1293afe5975d5e32fb5e0dceaf3 Mon Sep 17 00:00:00 2001 From: qianlongxu Date: Mon, 22 May 2023 18:19:11 +0800 -Subject: [PATCH 07/15] Correct the wrong codecpar->codec_id which read +Subject: [PATCH 10/15] Correct the wrong codecpar->codec_id which read from MIME of ID3tags, but the real data was encoded in PNG/JPEG/TIFF --- diff --git a/shell/patches/ffmpeg-release-5.1/0009-support-to-parse-bluray-protocol-all-tracks-la.patch b/shell/patches/ffmpeg-release-5.1/0011-support-to-parse-bluray-protocol-all-tra.patch similarity index 80% rename from shell/patches/ffmpeg-release-5.1/0009-support-to-parse-bluray-protocol-all-tracks-la.patch rename to shell/patches/ffmpeg-release-5.1/0011-support-to-parse-bluray-protocol-all-tra.patch index 313e6365fe..5dba662307 100644 --- a/shell/patches/ffmpeg-release-5.1/0009-support-to-parse-bluray-protocol-all-tracks-la.patch +++ b/shell/patches/ffmpeg-release-5.1/0011-support-to-parse-bluray-protocol-all-tra.patch @@ -1,17 +1,18 @@ -From 2ff4daa1d2b6edb4811bf52cdab14dad2b796b5d Mon Sep 17 00:00:00 2001 +From 563860663a921f07bcf868ddb9f91b64ce244432 Mon Sep 17 00:00:00 2001 From: qianlongxu -Date: Mon, 12 Jun 2023 16:32:25 +0800 -Subject: [PATCH 09/15] support to parse bluray protocol all tracks language meta +Date: Tue, 23 Jan 2024 14:33:15 +0800 +Subject: [PATCH 11/15] support to parse bluray protocol all tracks + language meta --- - libavformat/bluray.c | 65 ++++++++++++++++++++++++++++++++++++++++++++ - libavformat/bluray.h | 29 ++++++++++++++++++++ - libavformat/demux.c | 14 ++++++++++ - 3 files changed, 108 insertions(+) + libavformat/bluray.c | 81 ++++++++++++++++++++++++++++++++++++++++++++ + libavformat/bluray.h | 29 ++++++++++++++++ + libavformat/demux.c | 14 ++++++++ + 3 files changed, 124 insertions(+) create mode 100644 libavformat/bluray.h diff --git a/libavformat/bluray.c b/libavformat/bluray.c -index 635c4f1..644166f 100644 +index 635c4f1..8814dc2 100644 --- a/libavformat/bluray.c +++ b/libavformat/bluray.c @@ -26,6 +26,8 @@ @@ -31,7 +32,32 @@ index 635c4f1..644166f 100644 } BlurayContext; #define OFFSET(x) offsetof(BlurayContext, x) -@@ -160,6 +163,7 @@ static int bluray_open(URLContext *h, const char *path, int flags) +@@ -111,8 +114,24 @@ static int bluray_close(URLContext *h) + return 0; + } + ++#ifdef DEBUG_BLURAY ++#include ++#define BLURAY_DEBUG_MASK 0xFFFFF //(0xFFFFF & ~DBG_STREAM) ++ ++static void bluray_DebugHandler(const char *psz) ++{ ++ size_t len = strlen(psz); ++ if(len < 1) return; ++ av_log(NULL, AV_LOG_DEBUG, "[bluray] %s\n",psz); ++} ++#endif ++ + static int bluray_open(URLContext *h, const char *path, int flags) + { ++#ifdef DEBUG_BLURAY ++ bd_set_debug_mask(BLURAY_DEBUG_MASK); ++ bd_set_debug_handler(bluray_DebugHandler); ++#endif + BlurayContext *bd = h->priv_data; + int num_title_idx; + const char *diskname = path; +@@ -160,6 +179,7 @@ static int bluray_open(URLContext *h, const char *path, int flags) if (info->duration > duration) { bd->playlist = info->playlist; @@ -39,7 +65,7 @@ index 635c4f1..644166f 100644 duration = info->duration; } -@@ -223,6 +227,67 @@ static int64_t bluray_seek(URLContext *h, int64_t pos, int whence) +@@ -223,6 +243,67 @@ static int64_t bluray_seek(URLContext *h, int64_t pos, int whence) return AVERROR(EINVAL); } @@ -144,7 +170,7 @@ index 0000000..dfd3cb5 +#endif /* AVFORMAT_BLURAY_H */ \ No newline at end of file diff --git a/libavformat/demux.c b/libavformat/demux.c -index 1620716..d518515 100644 +index 06216eb..48999b9 100644 --- a/libavformat/demux.c +++ b/libavformat/demux.c @@ -38,6 +38,9 @@ @@ -157,7 +183,7 @@ index 1620716..d518515 100644 #include "avformat.h" #include "avio_internal.h" -@@ -335,6 +338,17 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, +@@ -348,6 +351,17 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, ff_id3v2_free_extra_meta(&id3v2_extra_meta); } diff --git a/shell/patches/ffmpeg-release-5.1/0010-mov-support-heic-demuxer-refer-https-github.co.patch b/shell/patches/ffmpeg-release-5.1/0012-mov-support-heic-demuxer-https-git.patch similarity index 99% rename from shell/patches/ffmpeg-release-5.1/0010-mov-support-heic-demuxer-refer-https-github.co.patch rename to shell/patches/ffmpeg-release-5.1/0012-mov-support-heic-demuxer-https-git.patch index dc1f514e74..8eb79190cd 100644 --- a/shell/patches/ffmpeg-release-5.1/0010-mov-support-heic-demuxer-refer-https-github.co.patch +++ b/shell/patches/ffmpeg-release-5.1/0012-mov-support-heic-demuxer-https-git.patch @@ -1,7 +1,8 @@ -From 7bce220aacde2614c3e777767e39b588783f7966 Mon Sep 17 00:00:00 2001 +From 13ac53d7cea87c6cf1fb5eaf2344a6cd16c254e8 Mon Sep 17 00:00:00 2001 From: qianlongxu Date: Mon, 12 Jun 2023 18:18:05 +0800 -Subject: [PATCH 10/15] [PATCH] mov support heic demuxer (https://github.com/bluez-sh/FFmpeg/commit/9a885cddb3550ab863a60d02c5fb78e4ae206cf1) +Subject: [PATCH 12/15] mov support heic demuxer + (https://github.com/bluez-sh/FFmpeg/commit/9a885cddb3550ab863a60d02c5fb78e4ae206cf1) --- libavcodec/packet.h | 15 + diff --git a/shell/patches/ffmpeg-release-5.1/0011-avformat-mov-fix-to-detect-if-stream-position-.patch b/shell/patches/ffmpeg-release-5.1/0013-avformat-mov-fix-to-detect-if-stream-pos.patch similarity index 59% rename from shell/patches/ffmpeg-release-5.1/0011-avformat-mov-fix-to-detect-if-stream-position-.patch rename to shell/patches/ffmpeg-release-5.1/0013-avformat-mov-fix-to-detect-if-stream-pos.patch index b68100316f..4a312153aa 100644 --- a/shell/patches/ffmpeg-release-5.1/0011-avformat-mov-fix-to-detect-if-stream-position-.patch +++ b/shell/patches/ffmpeg-release-5.1/0013-avformat-mov-fix-to-detect-if-stream-pos.patch @@ -1,38 +1,14 @@ -From 58c75976a4af13ad3b39b142e9f4ae957ee883f7 Mon Sep 17 00:00:00 2001 +From f6f27781ff29d6b6d9f812d0a01d990fc91ce3e0 Mon Sep 17 00:00:00 2001 From: qianlongxu -Date: Wed, 14 Jun 2023 17:56:07 +0800 -Subject: [PATCH 11/15] avformat mov fix to detect if stream position has been reset (https://patchwork.ffmpeg.org/project/ffmpeg/patch/20200424152042.29383-3-hello.vectronic@gmail.com/) +Date: Tue, 23 Jan 2024 14:16:01 +0800 +Subject: [PATCH 13/15] avformat mov fix to detect if stream position + has been reset + (https://patchwork.ffmpeg.org/project/ffmpeg/patch/20200424152042.29383-3-hello.vectronic@gmail.com/) --- - libavformat/hls.c | 7 +++++-- libavformat/mov.c | 35 ++++++++++++++++++++++++++++++++--- - 2 files changed, 37 insertions(+), 5 deletions(-) + 1 file changed, 32 insertions(+), 3 deletions(-) -diff --git a/libavformat/hls.c b/libavformat/hls.c -index 2e3bff6..6858e60 100644 ---- a/libavformat/hls.c -+++ b/libavformat/hls.c -@@ -2382,7 +2382,10 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) - ts_diff = av_rescale_rnd(pkt_ts, AV_TIME_BASE, - tb.den, AV_ROUND_DOWN) - - pls->seek_timestamp; -- if (ts_diff >= 0 && (pls->seek_flags & AVSEEK_FLAG_ANY || -+ -+ /* If AVSEEK_FLAG_ANY, keep reading until ts_diff is greater than 0 -+ * otherwise return the first keyframe encountered */ -+ if (ts_diff >= 0 && (pls->seek_flags & AVSEEK_FLAG_ANY || - pls->pkt->flags & AV_PKT_FLAG_KEY)) { - pls->seek_timestamp = AV_NOPTS_VALUE; - break; -@@ -2534,7 +2537,7 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, - pb->eof_reached = 0; - /* Clear any buffered data */ - pb->buf_end = pb->buf_ptr = pb->buffer; -- /* Reset the pos, to let the mpegts demuxer know we've seeked. */ -+ /* Reset the pos, to let the mpegts/mov demuxer know we've seeked. */ - pb->pos = 0; - /* Flush the packet queue of the subdemuxer. */ - ff_read_frame_flush(pls->ctx); diff --git a/libavformat/mov.c b/libavformat/mov.c index 6b19678..7409c54 100644 --- a/libavformat/mov.c diff --git a/shell/patches/ffmpeg-release-5.1/0013-restore-ijk-custom-protocols-except-longurl.patch b/shell/patches/ffmpeg-release-5.1/0013-restore-ijk-custom-protocols-except-longurl.patch deleted file mode 100644 index 0a1bf063a9..0000000000 --- a/shell/patches/ffmpeg-release-5.1/0013-restore-ijk-custom-protocols-except-longurl.patch +++ /dev/null @@ -1,641 +0,0 @@ -From 6a6f96b5b870260bb5c8d70fcc602417aab2a7af Mon Sep 17 00:00:00 2001 -From: qianlongxu -Date: Tue, 8 Aug 2023 14:53:18 +0800 -Subject: [PATCH 13/15] restore ijk custom protocols except longurl - ---- - libavcodec/Makefile | 1 + - libavformat/Makefile | 9 ++ - libavformat/allformats.c | 3 + - libavformat/async.c | 2 +- - libavformat/avformat.h | 5 + - libavformat/demux.c | 12 +++ - libavformat/ijkutils.c | 97 ++++++++++++++++++ - libavformat/protocols.c | 6 ++ - libavutil/Makefile | 3 + - libavutil/application.c | 215 +++++++++++++++++++++++++++++++++++++++ - libavutil/application.h | 122 ++++++++++++++++++++++ - 11 files changed, 474 insertions(+), 1 deletion(-) - create mode 100644 libavformat/ijkutils.c - create mode 100644 libavutil/application.c - create mode 100644 libavutil/application.h - -diff --git a/libavcodec/Makefile b/libavcodec/Makefile -index 457ec58..81f2d80 100644 ---- a/libavcodec/Makefile -+++ b/libavcodec/Makefile -@@ -26,6 +26,7 @@ HEADERS = ac3_parser.h \ - videotoolbox.h \ - vorbis_parser.h \ - xvmc.h \ -+ packet_internal.h \ - - OBJS = ac3_parser.o \ - adts_parser.o \ -diff --git a/libavformat/Makefile b/libavformat/Makefile -index 6c6b779..dfbed75 100644 ---- a/libavformat/Makefile -+++ b/libavformat/Makefile -@@ -5,6 +5,14 @@ HEADERS = avformat.h \ - avio.h \ - version.h \ - version_major.h \ -+ avc.h \ -+ url.h \ -+ internal.h \ -+ avio_internal.h \ -+ flv.h \ -+ id3v2.h \ -+ os_support.h \ -+ metadata.h \ - - OBJS = allformats.o \ - avformat.o \ -@@ -29,6 +37,7 @@ OBJS = allformats.o \ - url.o \ - utils.o \ - version.o \ -+ ijkutils.o \ - - OBJS-$(HAVE_LIBC_MSVCRT) += file_open.o - -diff --git a/libavformat/allformats.c b/libavformat/allformats.c -index 32698b8..17014e5 100644 ---- a/libavformat/allformats.c -+++ b/libavformat/allformats.c -@@ -544,6 +544,9 @@ extern const AVInputFormat ff_libmodplug_demuxer; - extern const AVInputFormat ff_libopenmpt_demuxer; - extern const AVInputFormat ff_vapoursynth_demuxer; - -+extern AVInputFormat ff_ijklivehook_demuxer; -+extern AVInputFormat ff_ijklas_demuxer; -+ - #include "libavformat/muxer_list.c" - #include "libavformat/demuxer_list.c" - -diff --git a/libavformat/async.c b/libavformat/async.c -index 3c6f89c..8c35dd0 100644 ---- a/libavformat/async.c -+++ b/libavformat/async.c -@@ -487,7 +487,7 @@ static const AVClass async_context_class = { - .version = LIBAVUTIL_VERSION_INT, - }; - --const URLProtocol ff_async_protocol = { -+URLProtocol ff_async_protocol = { - .name = "async", - .url_open2 = async_open, - .url_read = async_read, -diff --git a/libavformat/avformat.h b/libavformat/avformat.h -index f12fa7d..f403aa7 100644 ---- a/libavformat/avformat.h -+++ b/libavformat/avformat.h -@@ -728,6 +728,11 @@ typedef struct AVInputFormat { - */ - int (*read_header)(struct AVFormatContext *); - -+ /** -+ * Used by format which open further nested input. -+ */ -+ int (*read_header2)(struct AVFormatContext *, AVDictionary **options); -+ - /** - * Read one packet and put it in 'pkt'. pts and flags are also - * set. 'avformat_new_stream' can be called only if the flag -diff --git a/libavformat/demux.c b/libavformat/demux.c -index d518515..bccd5a5 100644 ---- a/libavformat/demux.c -+++ b/libavformat/demux.c -@@ -226,6 +226,7 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, - AVFormatContext *s = *ps; - FFFormatContext *si; - AVDictionary *tmp = NULL; -+ AVDictionary *tmp2 = NULL; - ID3v2ExtraMeta *id3v2_extra_meta = NULL; - int ret = 0; - -@@ -309,6 +310,15 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, - if (s->pb) - ff_id3v2_read_dict(s->pb, &si->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); - -+ if (s->iformat->read_header2) { -+ if (options) -+ av_dict_copy(&tmp2,*options, 0); -+ if ((ret = s->iformat->read_header2(s, &tmp2)) < 0) { -+ if (s->iformat->flags_internal & FF_FMT_INIT_CLEANUP) -+ goto close; -+ goto fail; -+ } -+ } - if (s->iformat->read_header) - if ((ret = s->iformat->read_header(s)) < 0) { - if (s->iformat->flags_internal & FF_FMT_INIT_CLEANUP) -@@ -362,6 +372,7 @@ int avformat_open_input(AVFormatContext **ps, const char *filename, - if (options) { - av_dict_free(options); - *options = tmp; -+ av_dict_free(&tmp2); - } - *ps = s; - return 0; -@@ -372,6 +383,7 @@ close: - fail: - ff_id3v2_free_extra_meta(&id3v2_extra_meta); - av_dict_free(&tmp); -+ av_dict_free(&tmp2); - if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO)) - avio_closep(&s->pb); - avformat_free_context(s); -diff --git a/libavformat/ijkutils.c b/libavformat/ijkutils.c -new file mode 100644 -index 0000000..25faad1 ---- /dev/null -+++ b/libavformat/ijkutils.c -@@ -0,0 +1,97 @@ -+/* -+ * utils.c -+ * -+ * Copyright (c) 2003 Fabrice Bellard -+ * Copyright (c) 2013 Zhang Rui -+ * -+ * This file is part of ijkPlayer. -+ * -+ * ijkPlayer is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU Lesser General Public -+ * License as published by the Free Software Foundation; either -+ * version 2.1 of the License, or (at your option) any later version. -+ * -+ * ijkPlayer is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -+ * Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public -+ * License along with ijkPlayer; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -+ */ -+ -+#include -+#include "url.h" -+#include "avformat.h" -+ -+ -+#define IJK_FF_PROTOCOL(x) \ -+extern URLProtocol ff_##x##_protocol; \ -+int ijkav_register_##x##_protocol(URLProtocol *protocol, int protocol_size); \ -+int ijkav_register_##x##_protocol(URLProtocol *protocol, int protocol_size) \ -+{ \ -+ if (protocol_size != sizeof(URLProtocol)) { \ -+ av_log(NULL, AV_LOG_ERROR, "ijkav_register_##x##_protocol: ABI mismatch.\n"); \ -+ return -1; \ -+ } \ -+ memcpy(&ff_##x##_protocol, protocol, protocol_size); \ -+ return 0; \ -+} -+ -+#define IJK_DUMMY_PROTOCOL(x) \ -+IJK_FF_PROTOCOL(x); \ -+static const AVClass ijk_##x##_context_class = { \ -+ .class_name = #x, \ -+ .item_name = av_default_item_name, \ -+ .version = LIBAVUTIL_VERSION_INT, \ -+ }; \ -+ \ -+URLProtocol ff_##x##_protocol = { \ -+ .name = #x, \ -+ .url_open2 = ijkdummy_open, \ -+ .priv_data_size = 1, \ -+ .priv_data_class = &ijk_##x##_context_class, \ -+}; -+ -+static int ijkdummy_open(URLContext *h, const char *arg, int flags, AVDictionary **options) -+{ -+ return -1; -+} -+ -+IJK_FF_PROTOCOL(async); -+IJK_DUMMY_PROTOCOL(ijkmediadatasource); -+IJK_DUMMY_PROTOCOL(ijkhttphook); -+IJK_DUMMY_PROTOCOL(ijksegment); -+IJK_DUMMY_PROTOCOL(ijktcphook); -+IJK_DUMMY_PROTOCOL(ijkio); -+ -+#define IJK_FF_DEMUXER(x) \ -+extern AVInputFormat ff_##x##_demuxer; \ -+int ijkav_register_##x##_demuxer(AVInputFormat *demuxer, int demuxer_size); \ -+int ijkav_register_##x##_demuxer(AVInputFormat *demuxer, int demuxer_size) \ -+{ \ -+ if (demuxer_size != sizeof(AVInputFormat)) { \ -+ av_log(NULL, AV_LOG_ERROR, "ijkav_register_##x##_demuxer: ABI mismatch.\n"); \ -+ return -1; \ -+ } \ -+ memcpy(&ff_##x##_demuxer, demuxer, demuxer_size); \ -+ return 0; \ -+} -+ -+#define IJK_DUMMY_DEMUXER(x) \ -+IJK_FF_DEMUXER(x); \ -+static const AVClass ijk_##x##_demuxer_class = { \ -+ .class_name = #x, \ -+ .item_name = av_default_item_name, \ -+ .version = LIBAVUTIL_VERSION_INT, \ -+ }; \ -+ \ -+AVInputFormat ff_##x##_demuxer = { \ -+ .name = #x, \ -+ .priv_data_size = 1, \ -+ .priv_class = &ijk_##x##_demuxer_class, \ -+}; -+ -+IJK_DUMMY_DEMUXER(ijklivehook); -+IJK_DUMMY_DEMUXER(ijklas); -diff --git a/libavformat/protocols.c b/libavformat/protocols.c -index 6ee62a5..8c67f34 100644 ---- a/libavformat/protocols.c -+++ b/libavformat/protocols.c -@@ -74,6 +74,12 @@ extern const URLProtocol ff_libzmq_protocol; - extern const URLProtocol ff_ipfs_protocol; - extern const URLProtocol ff_ipns_protocol; - -+extern const URLProtocol ff_ijkhttphook_protocol; -+extern const URLProtocol ff_ijkmediadatasource_protocol; -+extern const URLProtocol ff_ijksegment_protocol; -+extern const URLProtocol ff_ijktcphook_protocol; -+extern const URLProtocol ff_ijkio_protocol; -+ - #include "libavformat/protocol_list.c" - - const AVClass *ff_urlcontext_child_class_iterate(void **iter) -diff --git a/libavutil/Makefile b/libavutil/Makefile -index 9435a0b..986627c 100644 ---- a/libavutil/Makefile -+++ b/libavutil/Makefile -@@ -9,6 +9,8 @@ HEADERS = adler32.h \ - avassert.h \ - avstring.h \ - avutil.h \ -+ application.h \ -+ thread.h \ - base64.h \ - blowfish.h \ - bprint.h \ -@@ -105,6 +107,7 @@ OBJS = adler32.o \ - audio_fifo.o \ - avstring.o \ - avsscanf.o \ -+ application.o \ - base64.o \ - blowfish.o \ - bprint.o \ -diff --git a/libavutil/application.c b/libavutil/application.c -new file mode 100644 -index 0000000..ec7c1f0 ---- /dev/null -+++ b/libavutil/application.c -@@ -0,0 +1,215 @@ -+/* -+ * copyright (c) 2016 Zhang Rui -+ * -+ * This file is part of FFmpeg. -+ * -+ * FFmpeg is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU Lesser General Public -+ * License as published by the Free Software Foundation; either -+ * version 2.1 of the License, or (at your option) any later version. -+ * -+ * FFmpeg is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -+ * Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public -+ * License along with FFmpeg; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -+ */ -+ -+#include "application.h" -+#include "../libavformat/network.h" -+#include "avstring.h" -+#include "mem.h" -+ -+void av_application_on_io_traffic(AVApplicationContext *h, AVAppIOTraffic *event); -+ -+int av_application_alloc(AVApplicationContext **ph, void *opaque) -+{ -+ AVApplicationContext *h = NULL; -+ -+ h = av_mallocz(sizeof(AVApplicationContext)); -+ if (!h) -+ return AVERROR(ENOMEM); -+ -+ h->opaque = opaque; -+ -+ *ph = h; -+ return 0; -+} -+ -+int av_application_open(AVApplicationContext **ph, void *opaque) -+{ -+ int ret = av_application_alloc(ph, opaque); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+void av_application_close(AVApplicationContext *h) -+{ -+ av_free(h); -+} -+ -+void av_application_closep(AVApplicationContext **ph) -+{ -+ if (!ph || !*ph) -+ return; -+ -+ av_application_close(*ph); -+ *ph = NULL; -+} -+ -+void av_application_on_http_event(AVApplicationContext *h, int event_type, AVAppHttpEvent *event) -+{ -+ if (h && h->func_on_app_event) -+ h->func_on_app_event(h, event_type, (void *)event, sizeof(AVAppHttpEvent)); -+} -+ -+void av_application_will_http_open(AVApplicationContext *h, void *obj, const char *url) -+{ -+ AVAppHttpEvent event = {0}; -+ -+ if (!h || !obj || !url) -+ return; -+ -+ event.obj = obj; -+ av_strlcpy(event.url, url, sizeof(event.url)); -+ -+ av_application_on_http_event(h, AVAPP_EVENT_WILL_HTTP_OPEN, &event); -+} -+ -+void av_application_did_http_open(AVApplicationContext *h, void *obj, const char *url, int error, int http_code, int64_t filesize) -+{ -+ AVAppHttpEvent event = {0}; -+ -+ if (!h || !obj || !url) -+ return; -+ -+ event.obj = obj; -+ av_strlcpy(event.url, url, sizeof(event.url)); -+ event.error = error; -+ event.http_code = http_code; -+ event.filesize = filesize; -+ -+ av_application_on_http_event(h, AVAPP_EVENT_DID_HTTP_OPEN, &event); -+} -+ -+void av_application_will_http_seek(AVApplicationContext *h, void *obj, const char *url, int64_t offset) -+{ -+ AVAppHttpEvent event = {0}; -+ -+ if (!h || !obj || !url) -+ return; -+ -+ event.obj = obj; -+ event.offset = offset; -+ av_strlcpy(event.url, url, sizeof(event.url)); -+ -+ av_application_on_http_event(h, AVAPP_EVENT_WILL_HTTP_SEEK, &event); -+} -+ -+void av_application_did_http_seek(AVApplicationContext *h, void *obj, const char *url, int64_t offset, int error, int http_code) -+{ -+ AVAppHttpEvent event = {0}; -+ -+ if (!h || !obj || !url) -+ return; -+ -+ event.obj = obj; -+ event.offset = offset; -+ av_strlcpy(event.url, url, sizeof(event.url)); -+ event.error = error; -+ event.http_code = http_code; -+ -+ av_application_on_http_event(h, AVAPP_EVENT_DID_HTTP_SEEK, &event); -+} -+ -+void av_application_on_io_traffic(AVApplicationContext *h, AVAppIOTraffic *event) -+{ -+ if (h && h->func_on_app_event) -+ h->func_on_app_event(h, AVAPP_EVENT_IO_TRAFFIC, (void *)event, sizeof(AVAppIOTraffic)); -+} -+ -+int av_application_on_io_control(AVApplicationContext *h, int event_type, AVAppIOControl *control) -+{ -+ if (h && h->func_on_app_event) -+ return h->func_on_app_event(h, event_type, (void *)control, sizeof(AVAppIOControl)); -+ return 0; -+} -+ -+int av_application_on_tcp_will_open(AVApplicationContext *h) -+{ -+ if (h && h->func_on_app_event) { -+ AVAppTcpIOControl control = {0}; -+ return h->func_on_app_event(h, AVAPP_CTRL_WILL_TCP_OPEN, (void *)&control, sizeof(AVAppTcpIOControl)); -+ } -+ return 0; -+} -+ -+// only callback returns error -+int av_application_on_tcp_did_open(AVApplicationContext *h, int error, int fd, AVAppTcpIOControl *control) -+{ -+ struct sockaddr_storage so_stg; -+ int ret = 0; -+ socklen_t so_len = sizeof(so_stg); -+ int so_family; -+ char *so_ip_name = control->ip; -+ -+ if (!h || !h->func_on_app_event || fd <= 0) -+ return 0; -+ -+ ret = getpeername(fd, (struct sockaddr *)&so_stg, &so_len); -+ if (ret) -+ return 0; -+ control->error = error; -+ control->fd = fd; -+ -+ so_family = ((struct sockaddr*)&so_stg)->sa_family; -+ switch (so_family) { -+ case AF_INET: { -+ struct sockaddr_in* in4 = (struct sockaddr_in*)&so_stg; -+ if (inet_ntop(AF_INET, &(in4->sin_addr), so_ip_name, sizeof(control->ip))) { -+ control->family = AF_INET; -+ control->port = in4->sin_port; -+ } -+ break; -+ } -+ case AF_INET6: { -+ struct sockaddr_in6* in6 = (struct sockaddr_in6*)&so_stg; -+ if (inet_ntop(AF_INET6, &(in6->sin6_addr), so_ip_name, sizeof(control->ip))) { -+ control->family = AF_INET6; -+ control->port = in6->sin6_port; -+ } -+ break; -+ } -+ } -+ -+ return h->func_on_app_event(h, AVAPP_CTRL_DID_TCP_OPEN, (void *)control, sizeof(AVAppTcpIOControl)); -+} -+ -+void av_application_on_async_statistic(AVApplicationContext *h, AVAppAsyncStatistic *statistic) -+{ -+ if (h && h->func_on_app_event) -+ h->func_on_app_event(h, AVAPP_EVENT_ASYNC_STATISTIC, (void *)statistic, sizeof(AVAppAsyncStatistic)); -+} -+ -+void av_application_on_async_read_speed(AVApplicationContext *h, AVAppAsyncReadSpeed *speed) -+{ -+ if (h && h->func_on_app_event) -+ h->func_on_app_event(h, AVAPP_EVENT_ASYNC_READ_SPEED, (void *)speed, sizeof(AVAppAsyncReadSpeed)); -+} -+ -+void av_application_did_io_tcp_read(AVApplicationContext *h, void *obj, int bytes) -+{ -+ AVAppIOTraffic event = {0}; -+ if (!h || !obj || bytes <= 0) -+ return; -+ -+ event.obj = obj; -+ event.bytes = bytes; -+ -+ av_application_on_io_traffic(h, &event); -+} -diff --git a/libavutil/application.h b/libavutil/application.h -new file mode 100644 -index 0000000..b64cb39 ---- /dev/null -+++ b/libavutil/application.h -@@ -0,0 +1,122 @@ -+/* -+ * copyright (c) 2016 Zhang Rui -+ * -+ * This file is part of FFmpeg. -+ * -+ * FFmpeg is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU Lesser General Public -+ * License as published by the Free Software Foundation; either -+ * version 2.1 of the License, or (at your option) any later version. -+ * -+ * FFmpeg is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -+ * Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public -+ * License along with FFmpeg; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -+ */ -+ -+#ifndef AVUTIL_APPLICATION_H -+#define AVUTIL_APPLICATION_H -+ -+#include -+#include "libavutil/log.h" -+ -+#define AVAPP_EVENT_WILL_HTTP_OPEN 1 //AVAppHttpEvent -+#define AVAPP_EVENT_DID_HTTP_OPEN 2 //AVAppHttpEvent -+#define AVAPP_EVENT_WILL_HTTP_SEEK 3 //AVAppHttpEvent -+#define AVAPP_EVENT_DID_HTTP_SEEK 4 //AVAppHttpEvent -+ -+#define AVAPP_EVENT_ASYNC_STATISTIC 0x11000 //AVAppAsyncStatistic -+#define AVAPP_EVENT_ASYNC_READ_SPEED 0x11001 //AVAppAsyncReadSpeed -+#define AVAPP_EVENT_IO_TRAFFIC 0x12204 //AVAppIOTraffic -+ -+#define AVAPP_CTRL_WILL_TCP_OPEN 0x20001 //AVAppTcpIOControl -+#define AVAPP_CTRL_DID_TCP_OPEN 0x20002 //AVAppTcpIOControl -+ -+#define AVAPP_CTRL_WILL_HTTP_OPEN 0x20003 //AVAppIOControl -+#define AVAPP_CTRL_WILL_LIVE_OPEN 0x20005 //AVAppIOControl -+ -+#define AVAPP_CTRL_WILL_CONCAT_SEGMENT_OPEN 0x20007 //AVAppIOControl -+ -+typedef struct AVAppIOControl { -+ size_t size; -+ char url[4096]; /* in, out */ -+ int segment_index; /* in, default = 0 */ -+ int retry_counter; /* in */ -+ -+ int is_handled; /* out, default = false */ -+ int is_url_changed; /* out, default = false */ -+} AVAppIOControl; -+ -+typedef struct AVAppTcpIOControl { -+ int error; -+ int family; -+ char ip[96]; -+ int port; -+ int fd; -+} AVAppTcpIOControl; -+ -+typedef struct AVAppAsyncStatistic { -+ size_t size; -+ int64_t buf_backwards; -+ int64_t buf_forwards; -+ int64_t buf_capacity; -+} AVAppAsyncStatistic; -+ -+typedef struct AVAppAsyncReadSpeed { -+ size_t size; -+ int is_full_speed; -+ int64_t io_bytes; -+ int64_t elapsed_milli; -+} AVAppAsyncReadSpeed; -+ -+typedef struct AVAppHttpEvent -+{ -+ void *obj; -+ char url[4096]; -+ int64_t offset; -+ int error; -+ int http_code; -+ int64_t filesize; -+} AVAppHttpEvent; -+ -+typedef struct AVAppIOTraffic -+{ -+ void *obj; -+ int bytes; -+} AVAppIOTraffic; -+ -+typedef struct AVApplicationContext AVApplicationContext; -+struct AVApplicationContext { -+ const AVClass *av_class; /**< information for av_log(). Set by av_application_open(). */ -+ void *opaque; /**< user data. */ -+ -+ int (*func_on_app_event)(AVApplicationContext *h, int event_type ,void *obj, size_t size); -+}; -+ -+int av_application_alloc(AVApplicationContext **ph, void *opaque); -+int av_application_open(AVApplicationContext **ph, void *opaque); -+void av_application_close(AVApplicationContext *h); -+void av_application_closep(AVApplicationContext **ph); -+ -+void av_application_on_http_event(AVApplicationContext *h, int event_type, AVAppHttpEvent *event); -+void av_application_will_http_open(AVApplicationContext *h, void *obj, const char *url); -+void av_application_did_http_open(AVApplicationContext *h, void *obj, const char *url, int error, int http_code, int64_t filesize); -+void av_application_will_http_seek(AVApplicationContext *h, void *obj, const char *url, int64_t offset); -+void av_application_did_http_seek(AVApplicationContext *h, void *obj, const char *url, int64_t offset, int error, int http_code); -+ -+void av_application_did_io_tcp_read(AVApplicationContext *h, void *obj, int bytes); -+ -+int av_application_on_io_control(AVApplicationContext *h, int event_type, AVAppIOControl *control); -+ -+int av_application_on_tcp_will_open(AVApplicationContext *h); -+int av_application_on_tcp_did_open(AVApplicationContext *h, int error, int fd, AVAppTcpIOControl *control); -+ -+void av_application_on_async_statistic(AVApplicationContext *h, AVAppAsyncStatistic *statistic); -+void av_application_on_async_read_speed(AVApplicationContext *h, AVAppAsyncReadSpeed *speed); -+ -+ -+#endif /* AVUTIL_APPLICATION_H */ --- -2.39.3 (Apple Git-145) - diff --git a/shell/patches/ffmpeg-release-5.1/0012-avformat-mpegts-index-only-keyframes-to-ensure.patch b/shell/patches/ffmpeg-release-5.1/0014-avformat-mpegts-index-only-keyframes-to-.patch similarity index 93% rename from shell/patches/ffmpeg-release-5.1/0012-avformat-mpegts-index-only-keyframes-to-ensure.patch rename to shell/patches/ffmpeg-release-5.1/0014-avformat-mpegts-index-only-keyframes-to-.patch index 9c82b43fdd..82b0d6249c 100644 --- a/shell/patches/ffmpeg-release-5.1/0012-avformat-mpegts-index-only-keyframes-to-ensure.patch +++ b/shell/patches/ffmpeg-release-5.1/0014-avformat-mpegts-index-only-keyframes-to-.patch @@ -1,7 +1,7 @@ -From 713aa2ad7dd11a80e145cde0aca1e0c7dba38bc0 Mon Sep 17 00:00:00 2001 +From bcc2005d5eaed8dab062d1b27b20106eef7ef150 Mon Sep 17 00:00:00 2001 From: qianlongxu Date: Sun, 25 Jun 2023 14:33:48 +0800 -Subject: [PATCH 12/15] avformat/mpegts: index only keyframes to ensure +Subject: [PATCH 14/15] avformat/mpegts: index only keyframes to ensure accurate seeks --- diff --git a/shell/patches/ffmpeg-release-5.1/0014-record-bluray-debug-log.patch b/shell/patches/ffmpeg-release-5.1/0014-record-bluray-debug-log.patch deleted file mode 100644 index 4a3ff94519..0000000000 --- a/shell/patches/ffmpeg-release-5.1/0014-record-bluray-debug-log.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 7fc90c789db0167482da8dc300e7fc496109f44f Mon Sep 17 00:00:00 2001 -From: qianlongxu -Date: Wed, 13 Dec 2023 11:05:39 +0800 -Subject: [PATCH 14/15] record bluray debug log - ---- - libavformat/bluray.c | 16 ++++++++++++++++ - 1 file changed, 16 insertions(+) - -diff --git a/libavformat/bluray.c b/libavformat/bluray.c -index 644166f..8814dc2 100644 ---- a/libavformat/bluray.c -+++ b/libavformat/bluray.c -@@ -114,8 +114,24 @@ static int bluray_close(URLContext *h) - return 0; - } - -+#ifdef DEBUG_BLURAY -+#include -+#define BLURAY_DEBUG_MASK 0xFFFFF //(0xFFFFF & ~DBG_STREAM) -+ -+static void bluray_DebugHandler(const char *psz) -+{ -+ size_t len = strlen(psz); -+ if(len < 1) return; -+ av_log(NULL, AV_LOG_DEBUG, "[bluray] %s\n",psz); -+} -+#endif -+ - static int bluray_open(URLContext *h, const char *path, int flags) - { -+#ifdef DEBUG_BLURAY -+ bd_set_debug_mask(BLURAY_DEBUG_MASK); -+ bd_set_debug_handler(bluray_DebugHandler); -+#endif - BlurayContext *bd = h->priv_data; - int num_title_idx; - const char *diskname = path; --- -2.39.3 (Apple Git-145) - diff --git a/shell/patches/ffmpeg-release-5.1/0015-Adds-DVD-protocol-https-ffmpeg.org-pipermail-ffmpeg-.patch b/shell/patches/ffmpeg-release-5.1/0015-Adds-DVD-protocol-https-ffmpeg.org-piper.patch similarity index 99% rename from shell/patches/ffmpeg-release-5.1/0015-Adds-DVD-protocol-https-ffmpeg.org-pipermail-ffmpeg-.patch rename to shell/patches/ffmpeg-release-5.1/0015-Adds-DVD-protocol-https-ffmpeg.org-piper.patch index f313076d8d..9d2b1bcf70 100644 --- a/shell/patches/ffmpeg-release-5.1/0015-Adds-DVD-protocol-https-ffmpeg.org-pipermail-ffmpeg-.patch +++ b/shell/patches/ffmpeg-release-5.1/0015-Adds-DVD-protocol-https-ffmpeg.org-piper.patch @@ -1,4 +1,4 @@ -From d28a475d95f23f2898b903f21e286b68a97ade1b Mon Sep 17 00:00:00 2001 +From 024dafcc043587ec2a1c6d2c35255e657ddbb080 Mon Sep 17 00:00:00 2001 From: qianlongxu Date: Fri, 5 Jan 2024 16:03:17 +0800 Subject: [PATCH 15/15] Adds DVD protocol diff --git a/shell/tools/init-repo.sh b/shell/tools/init-repo.sh index 171ee53909..1dc01a6e62 100755 --- a/shell/tools/init-repo.sh +++ b/shell/tools/init-repo.sh @@ -94,7 +94,7 @@ function apply_patches() { if [[ -d "$patch_dir" ]]; then echo echo "== Applying patches: $(basename $patch_dir) → $(basename $PWD) ==" - git am --keep $patch_dir/*.patch + git am --whitespace=fix --keep $patch_dir/*.patch if [[ $? -ne 0 ]]; then echo 'Apply patches failed!' git am --skip diff --git a/shell/tools/sync-lastest-private.sh b/shell/tools/sync-lastest-private.sh index 0e185f6bc6..50a9c8987e 100755 --- a/shell/tools/sync-lastest-private.sh +++ b/shell/tools/sync-lastest-private.sh @@ -12,7 +12,8 @@ set -e THIS_DIR=$(DIRNAME=$(dirname "$0"); cd "$DIRNAME"; pwd) cd "$THIS_DIR" -cd ../build/extra + +lib=$1 function update() { @@ -27,13 +28,13 @@ function update() echo "$repo" git remote add github "$repo" fi - + echo '=== will pull all from github ===' git reset --hard git checkout master -B master git pull origin master git fetch github --tags - + echo '=== will push all branch to private ===' git push origin --tags git push origin --all @@ -42,9 +43,15 @@ function update() cd - } -for i in $(ls); -do - update "$i" -done; +cd ../build/extra +if [[ -z "$lib" ]];then + for lib in $(ls); + do + update "$lib" + done; +else + update "$lib" +fi + + -# update bluray diff --git a/version.sh b/version.sh index db4c360b11..37b8d46bf4 100755 --- a/version.sh +++ b/version.sh @@ -2,8 +2,8 @@ set -e -VERSION_CODE=1000500 -VERSION_NAME=0.10.5 +VERSION_CODE=1100000 +VERSION_NAME=0.11.0 VERSION_TARGET=$1 echo "alter version to $VERSION_NAME"