diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs
index 097cb9a..e72fa71 100644
--- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs
+++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs
@@ -77,6 +77,7 @@ public class ResString
     public static string cmd_uiLanguage => GetText("cmd_uiLanguage");
     public static string cmd_urlProcessorArgs => GetText("cmd_urlProcessorArgs");
     public static string cmd_useShakaPackager => GetText("cmd_useShakaPackager");
+    public static string cmd_useMp4decrypt => GetText("cmd_useMp4decrypt");
     public static string cmd_concurrentDownload => GetText("cmd_concurrentDownload");
     public static string cmd_useSystemProxy => GetText("cmd_useSystemProxy");
     public static string cmd_customProxy => GetText("cmd_customProxy");
diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs
index d424059..bc7efcc 100644
--- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs
+++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs
@@ -270,9 +270,9 @@ internal class StaticText
         ),
         ["cmd_keys"] = new TextContainer
         (
-            zhCN: "设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n对于KEY相同的情况可以直接输入 --key KEY",
-            zhTW: "設置解密密鑰, 程序調用mp4decrpyt/shaka-packager進行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n對於KEY相同的情況可以直接輸入 --key KEY",
-            enUS: "Set decryption key(s) to mp4decrypt/shaka-packager. format:\r\n--key KID1:KEY1 --key KID2:KEY2\r\nor use --key KEY if all tracks share the same key."
+            zhCN: "设置解密密钥, 程序调用mp4decrpyt/shaka-packager/ffmpeg进行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n对于KEY相同的情况可以直接输入 --key KEY",
+            zhTW: "設置解密密鑰, 程序調用mp4decrpyt/shaka-packager/ffmpeg進行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n對於KEY相同的情況可以直接輸入 --key KEY",
+            enUS: "Set decryption key(s) to mp4decrypt/shaka-packager/ffmpeg. format:\r\n--key KID1:KEY1 --key KID2:KEY2\r\nor use --key KEY if all tracks share the same key."
         ),
         ["cmd_keyText"] = new TextContainer
         (
@@ -468,9 +468,15 @@ internal class StaticText
         ),
         ["cmd_useShakaPackager"] = new TextContainer
         (
-            zhCN: "解密时使用shaka-packager替代mp4decrypt",
-            zhTW: "解密時使用shaka-packager替代mp4decrypt",
-            enUS: "Use shaka-packager instead of mp4decrypt to decrypt"
+            zhCN: "解密时使用shaka-packager替代ffmpeg",
+            zhTW: "解密時使用shaka-packager替代ffmpeg",
+            enUS: "Use shaka-packager instead of ffmpeg to decrypt"
+        ),
+        ["cmd_useMp4decrypt"] = new TextContainer
+        (
+            zhCN: "解密时使用mp4decrypt替代ffmpeg",
+            zhTW: "解密時使用mp4decrypt替代ffmpeg",
+            enUS: "Use mp4decrypt instead of ffmpeg to decrypt"
         ),
         ["cmd_concurrentDownload"] = new TextContainer
         (
@@ -744,9 +750,9 @@ internal class StaticText
         ),
         ["realTimeDecMessage"] = new TextContainer
         (
-            zhCN: "启用实时解密时,建议用shaka-packager而非mp4decrypt",
-            zhTW: "啟用即時解密時,建議用shaka-packager而非mp4decrypt",
-            enUS: "When enabling real-time decryption, it is recommended to use shaka-packager instead of mp4decrypt"
+            zhCN: "启用实时解密时,建议用shaka-packager而非mp4decrypt/ffmpeg",
+            zhTW: "啟用即時解密時,建議用shaka-packager而非mp4decrypt/ffmpeg",
+            enUS: "When enabling real-time decryption, it is recommended to use shaka-packager instead of mp4decrypt/ffmpeg"
         ),
         ["liveLimitReached"] = new TextContainer
         (
diff --git a/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs b/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs
index 61553a5..b188561 100644
--- a/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs
+++ b/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs
@@ -368,7 +368,7 @@ internal class HLSExtractor : IExtractor
             // #EXT-X-MAP
             else if (line.StartsWith(HLSTags.ext_x_map))
             {
-                if (playlist.MediaInit == null) 
+                if (playlist.MediaInit == null || hasAd) 
                 {
                     playlist.MediaInit = new MediaSegment()
                     {
diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs
index b50c915..f65b56a 100644
--- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs
+++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs
@@ -54,6 +54,7 @@ internal partial class CommandInvoker
     private static readonly Option<bool> AppendUrlParams = new(["--append-url-params"], description: ResString.cmd_appendUrlParams, getDefaultValue: () => false);
     private static readonly Option<bool> MP4RealTimeDecryption = new (["--mp4-real-time-decryption"], description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false);
     private static readonly Option<bool> UseShakaPackager = new (["--use-shaka-packager"], description: ResString.cmd_useShakaPackager, getDefaultValue: () => false);
+    private static readonly Option<bool> UseMp4Decrypt = new (["--use-mp4decrypt"], description: ResString.cmd_useMp4decrypt, getDefaultValue: () => false);
     private static readonly Option<bool> ForceAnsiConsole = new(["--force-ansi-console"], description: ResString.cmd_forceAnsiConsole);
     private static readonly Option<bool> NoAnsiColor = new(["--no-ansi-color"], description: ResString.cmd_noAnsiColor);
     private static readonly Option<string?> DecryptionBinaryPath = new(["--decryption-binary-path"], description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" };
@@ -523,6 +524,7 @@ internal partial class CommandInvoker
                 UrlProcessorArgs = bindingContext.ParseResult.GetValueForOption(UrlProcessorArgs),
                 MP4RealTimeDecryption = bindingContext.ParseResult.GetValueForOption(MP4RealTimeDecryption),
                 UseShakaPackager = bindingContext.ParseResult.GetValueForOption(UseShakaPackager),
+                UseMp4Decrypt = bindingContext.ParseResult.GetValueForOption(UseMp4Decrypt),
                 DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath),
                 FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath),
                 KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile),
@@ -615,7 +617,7 @@ internal partial class CommandInvoker
             Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, HttpRequestTimeout, ForceAnsiConsole, NoAnsiColor,AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
             BinaryMerge, UseFFmpegConcatDemuxer, DelAfterDone, NoDateInfo, NoLog, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
             FFmpegBinaryPath,
-            LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
+            LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, UseMp4Decrypt, MP4RealTimeDecryption,
             MaxSpeed,
             MuxAfterDone,
             CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt,
diff --git a/src/N_m3u8DL-RE/CommandLine/MyOption.cs b/src/N_m3u8DL-RE/CommandLine/MyOption.cs
index 4a783d8..8233c56 100644
--- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs
+++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs
@@ -141,6 +141,10 @@ internal class MyOption
     /// </summary>
     public bool UseShakaPackager { get; set; }
     /// <summary>
+    /// See: <see cref="CommandInvoker.UseMp4Decrypt"/>.
+    /// </summary>
+    public bool UseMp4Decrypt { get; set; }
+    /// <summary>
     /// See: <see cref="CommandInvoker.MuxAfterDone"/>.
     /// </summary>
     public bool MuxAfterDone { get; set; }
@@ -266,4 +270,13 @@ internal class MyOption
     /// See: <see cref="CommandInvoker.LiveFixVttByAudio"/>.
     /// </summary>
     public bool LiveFixVttByAudio { get; set; }
+    
+    public DecryptEngine GetDecryptEngine()
+    {
+        if (UseShakaPackager)
+            return DecryptEngine.SHAKA_PACKAGE;
+        if (UseMp4Decrypt)
+            return DecryptEngine.MP4DECRYPT;
+        return DecryptEngine.FFMPEG;
+    }
 }
\ No newline at end of file
diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs
index abd4c17..b3dfe1e 100644
--- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs
+++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs
@@ -13,6 +13,7 @@ using N_m3u8DL_RE.Util;
 using Spectre.Console;
 using System.Collections.Concurrent;
 using System.Text;
+using N_m3u8DL_RE.Enum;
 
 namespace N_m3u8DL_RE.DownloadManager;
 
@@ -39,9 +40,9 @@ internal class SimpleDownloadManager
         if (_key != null)
         {
             if (DownloaderConfig.MyOptions.Keys == null)
-                DownloaderConfig.MyOptions.Keys = new string[] { _key };
+                DownloaderConfig.MyOptions.Keys = [_key];
             else
-                DownloaderConfig.MyOptions.Keys = DownloaderConfig.MyOptions.Keys.Concat(new string[] { _key }).ToArray();
+                DownloaderConfig.MyOptions.Keys = [..DownloaderConfig.MyOptions.Keys, _key];
         }
     }
 
@@ -109,8 +110,8 @@ internal class SimpleDownloadManager
         var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
         var headers = DownloaderConfig.Headers;
 
-        // mp4decrypt
-        var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
+        var decryptionBinaryPath = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
+        var decryptEngine = DownloaderConfig.MyOptions.GetDecryptEngine();
         var mp4InitFile = "";
         var currentKID = "";
         var readInfo = false; // 是否读取过
@@ -171,7 +172,7 @@ internal class SimpleDownloadManager
                 currentKID = mp4Info.KID;
                 // try shaka packager, which can handle WebM
                 if (string.IsNullOrEmpty(currentKID) &&  DownloaderConfig.MyOptions.UseShakaPackager) {
-                    currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt);
+                    currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, decryptionBinaryPath);
                 }
                 // 从文件读取KEY
                 await SearchKeyAsync(currentKID);
@@ -180,7 +181,7 @@ internal class SimpleDownloadManager
                 {
                     var enc = result.ActualFilePath;
                     var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
-                    var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
+                    var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
                     if (dResult)
                     {
                         FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
@@ -229,7 +230,7 @@ internal class SimpleDownloadManager
                         // 需要重新解密init
                         var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
                         var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
-                        var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
+                        var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
                         if (dResult)
                         {
                             FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
@@ -243,7 +244,7 @@ internal class SimpleDownloadManager
                 }
                 // try shaka packager, which can handle WebM
                 if (string.IsNullOrEmpty(currentKID) &&  DownloaderConfig.MyOptions.UseShakaPackager) {
-                    currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt);
+                    currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, decryptionBinaryPath);
                 }
                 // 从文件读取KEY
                 await SearchKeyAsync(currentKID);
@@ -253,7 +254,7 @@ internal class SimpleDownloadManager
                     var enc = result.ActualFilePath;
                     var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
                     mp4Info = MP4DecryptUtil.GetMP4Info(enc);
-                    var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
+                    var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
                     if (dResult)
                     {
                         File.Delete(enc);
@@ -291,7 +292,7 @@ internal class SimpleDownloadManager
                 var enc = result.ActualFilePath;
                 var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
                 mp4Info = MP4DecryptUtil.GetMP4Info(enc);
-                var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
+                var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
                 if (dResult)
                 {
                     File.Delete(enc);
@@ -322,8 +323,8 @@ internal class SimpleDownloadManager
         if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0 && mp4InitFile != "")
         {
             File.Delete(mp4InitFile);
-            // shaka实时解密不需要init文件用于合并
-            if (DownloaderConfig.MyOptions.UseShakaPackager)
+            // shaka/ffmpeg实时解密不需要init文件用于合并
+            if (decryptEngine != DecryptEngine.MP4DECRYPT)
             {
                 FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _);
             }
@@ -590,7 +591,7 @@ internal class SimpleDownloadManager
             currentKID = MP4DecryptUtil.GetMP4Info(output).KID;
             // try shaka packager, which can handle WebM
             if (string.IsNullOrEmpty(currentKID) &&  DownloaderConfig.MyOptions.UseShakaPackager) {
-                currentKID = MP4DecryptUtil.ReadInitShaka(output, mp4decrypt);
+                currentKID = MP4DecryptUtil.ReadInitShaka(output, decryptionBinaryPath);
             }
             // 从文件读取KEY
             await SearchKeyAsync(currentKID);
@@ -602,8 +603,8 @@ internal class SimpleDownloadManager
             var enc = output;
             var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
             mp4Info = MP4DecryptUtil.GetMP4Info(enc);
-            Logger.InfoMarkUp($"[grey]Decrypting...[/]");
-            var result = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
+            Logger.InfoMarkUp($"[grey]Decrypting using {decryptEngine}...[/]");
+            var result = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
             if (result)
             {
                 File.Delete(enc);
@@ -653,8 +654,7 @@ internal class SimpleDownloadManager
         }
         progress.Columns(progressColumns);
 
-        if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager
-                                                             && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
+        if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, UseShakaPackager: false, Keys.Length: > 0 })
             Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
 
         await progress.StartAsync(async ctx =>
diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs
index e5c07ef..51eafd8 100644
--- a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs
+++ b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs
@@ -16,6 +16,7 @@ using System.Collections.Concurrent;
 using System.IO.Pipes;
 using System.Text;
 using System.Threading.Tasks.Dataflow;
+using N_m3u8DL_RE.Enum;
 
 namespace N_m3u8DL_RE.DownloadManager;
 
@@ -143,8 +144,7 @@ internal class SimpleLiveRecordManager2
     private async Task<bool> RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, BufferBlock<List<MediaSegment>> source)
     {
         var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
-        // mp4decrypt
-        var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
+        var decryptionBinaryPath = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
         var mp4InitFile = "";
         var currentKID = "";
         var readInfo = false; // 是否读取过
@@ -164,6 +164,7 @@ internal class SimpleLiveRecordManager2
         var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
         var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
         var headers = DownloaderConfig.Headers;
+        var decryptEngine = DownloaderConfig.MyOptions.GetDecryptEngine();
 
         Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
 
@@ -214,7 +215,7 @@ internal class SimpleLiveRecordManager2
                     {
                         var enc = result.ActualFilePath;
                         var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
-                        var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
+                        var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
                         if (dResult)
                         {
                             FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
@@ -274,7 +275,7 @@ internal class SimpleLiveRecordManager2
                             // 需要重新解密init
                             var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
                             var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
-                            var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
+                            var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
                             if (dResult)
                             {
                                 FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
@@ -293,7 +294,7 @@ internal class SimpleLiveRecordManager2
                     {
                         var enc = result.ActualFilePath;
                         var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
-                        var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
+                        var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
                         if (dResult)
                         {
                             File.Delete(enc);
@@ -336,7 +337,7 @@ internal class SimpleLiveRecordManager2
                 {
                     var enc = result.ActualFilePath;
                     var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
-                    var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
+                    var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
                     if (dResult)
                     {
                         File.Delete(enc);
@@ -554,10 +555,10 @@ internal class SimpleLiveRecordManager2
                     var files = FileDic.Where(f => f.Key != streamSpec.Playlist!.MediaInit).OrderBy(s => s.Key.Index).Select(f => f.Value).Select(v => v!.ActualFilePath).ToArray();
                     if (initResult != null && mp4InitFile != "")
                     {
-                        // shaka实时解密不需要init文件用于合并,mp4decrpyt需要
-                        if (!DownloaderConfig.MyOptions.UseShakaPackager)
+                        // shaka/ffmpeg实时解密不需要init文件用于合并,mp4decrpyt需要
+                        if (decryptEngine != DecryptEngine.MP4DECRYPT)
                         {
-                            files = new string[] { initResult.ActualFilePath }.Concat(files).ToArray();
+                            files = [initResult.ActualFilePath, ..files];
                         }
                     }
                     foreach (var inputFilePath in files)
@@ -839,8 +840,7 @@ internal class SimpleLiveRecordManager2
             DownloaderConfig.MyOptions.ConcurrentDownload = true;
             DownloaderConfig.MyOptions.MP4RealTimeDecryption = true;
             DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue;
-            if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager
-                                                                 && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
+            if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, UseShakaPackager: false, Keys.Length: > 0 })
                 Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
             var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
             if (limit != TimeSpan.MaxValue)
diff --git a/src/N_m3u8DL-RE/Enum/DecryptEngine.cs b/src/N_m3u8DL-RE/Enum/DecryptEngine.cs
new file mode 100644
index 0000000..6c01e3e
--- /dev/null
+++ b/src/N_m3u8DL-RE/Enum/DecryptEngine.cs
@@ -0,0 +1,8 @@
+namespace N_m3u8DL_RE.Enum;
+
+internal enum DecryptEngine
+{
+    MP4DECRYPT,
+    SHAKA_PACKAGE,
+    FFMPEG,
+}
\ No newline at end of file
diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs
index e9493f1..b10a852 100644
--- a/src/N_m3u8DL-RE/Program.cs
+++ b/src/N_m3u8DL-RE/Program.cs
@@ -105,11 +105,15 @@ internal class Program
         }
 
         // 检查互斥的选项
-
         if (option is { MuxAfterDone: false, MuxImports.Count: > 0 })
         {
             throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!");
         }
+        
+        if (option is { UseShakaPackager: true, UseMp4Decrypt: true })
+        {
+            throw new ArgumentException("UseShakaPackager and UseMp4Decrypt cannot be enabled simultaneously!");
+        }
 
         // LivePipeMux开启时 LiveRealTimeMerge必须开启
         if (option is { LivePipeMux: true, LiveRealTimeMerge: false })
@@ -154,13 +158,17 @@ internal class Program
                     option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4;
                     Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}");
                 }
-                else
+                else if (option.UseMp4Decrypt)
                 {
                     var file = GlobalUtil.FindExecutable("mp4decrypt");
                     if (file == null) throw new FileNotFoundException("mp4decrypt not found!");
                     option.DecryptionBinaryPath = file;
                     Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}");
                 }
+                else
+                {
+                    option.DecryptionBinaryPath = option.FFmpegBinaryPath;
+                }
             }
             else if (!File.Exists(option.DecryptionBinaryPath))
             {
diff --git a/src/N_m3u8DL-RE/Util/DownloadUtil.cs b/src/N_m3u8DL-RE/Util/DownloadUtil.cs
index 63e29e3..0e8bd5b 100644
--- a/src/N_m3u8DL-RE/Util/DownloadUtil.cs
+++ b/src/N_m3u8DL-RE/Util/DownloadUtil.cs
@@ -24,7 +24,7 @@ internal static class DownloadUtil
         else
         {
             var buffer = new byte[expect];
-            await inputStream.ReadAsync(buffer);
+            _ = await inputStream.ReadAsync(buffer);
             await outputStream.WriteAsync(buffer, 0, buffer.Length);
             speedContainer.Add(buffer.Length);
         }
diff --git a/src/N_m3u8DL-RE/Util/LanguageCodeUtil.cs b/src/N_m3u8DL-RE/Util/LanguageCodeUtil.cs
index c79876c..8e5546d 100644
--- a/src/N_m3u8DL-RE/Util/LanguageCodeUtil.cs
+++ b/src/N_m3u8DL-RE/Util/LanguageCodeUtil.cs
@@ -14,6 +14,7 @@ internal static class LanguageCodeUtil
 {
 
     private static readonly List<Language> ALL_LANGS = @"
+default;und;default;default
 af;afr;Afrikaans;Afrikaans
 af-ZA;afr;Afrikaans (South Africa);Afrikaans (South Africa)
 am;amh;Amharic;Amharic
diff --git a/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs b/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs
index 9b6bdd6..bada77f 100644
--- a/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs
+++ b/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs
@@ -1,16 +1,16 @@
 using Mp4SubtitleParser;
 using N_m3u8DL_RE.Common.Log;
 using N_m3u8DL_RE.Common.Resource;
-using N_m3u8DL_RE.Config;
 using System.Diagnostics;
 using System.Text.RegularExpressions;
+using N_m3u8DL_RE.Enum;
 
 namespace N_m3u8DL_RE.Util;
 
 internal static class MP4DecryptUtil
 {
     private static readonly string ZeroKid = "00000000000000000000000000000000";
-    public static async Task<bool> DecryptAsync(bool shakaPackager, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false)
+    public static async Task<bool> DecryptAsync(DecryptEngine decryptEngine, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false)
     {
         if (keys == null || keys.Length == 0) return false;
 
@@ -45,27 +45,27 @@ internal static class MP4DecryptUtil
             
         if (keyPair == null) return false;
 
-        // shakaPackager 无法单独解密init文件
-        if (source.EndsWith("_init.mp4") && shakaPackager) return false;
+        // shakaPackager/ffmpeg 无法单独解密init文件
+        if (source.EndsWith("_init.mp4") && decryptEngine != DecryptEngine.MP4DECRYPT) return false;
 
         string cmd;
 
         var tmpFile = "";
-        if (shakaPackager)
+        if (decryptEngine == DecryptEngine.SHAKA_PACKAGE)
         {
             var enc = source;
             // shakaPackager 手动构造文件
             if (init != "")
             {
                 tmpFile = Path.ChangeExtension(source, ".itmp");
-                MergeUtil.CombineMultipleFilesIntoSingleFile(new string[] { init, source }, tmpFile);
+                MergeUtil.CombineMultipleFilesIntoSingleFile([init, source], tmpFile);
                 enc = tmpFile;
             }
 
             cmd = $"--quiet --enable_raw_key_decryption input=\"{enc}\",stream=0,output=\"{dest}\" " +
                   $"--keys {(trackId != null ? $"label={trackId}:" : "")}key_id={(trackId != null ? ZeroKid : kid)}:key={keyPair.Split(':')[1]}";
         }
-        else
+        else if (decryptEngine == DecryptEngine.MP4DECRYPT)
         {
             if (trackId == null)
             {
@@ -81,6 +81,19 @@ internal static class MP4DecryptUtil
             }
             cmd += $" \"{source}\" \"{dest}\"";
         }
+        else
+        {
+            var enc = source;
+            // ffmpeg实时解密 手动构造文件
+            if (init != "")
+            {
+                tmpFile = Path.ChangeExtension(source, ".itmp");
+                MergeUtil.CombineMultipleFilesIntoSingleFile([init, source], tmpFile);
+                enc = tmpFile;
+            }
+            
+            cmd = $"-loglevel error -nostdin -decryption_key {keyPair.Split(':')[1]} -i \"{enc}\" -c copy \"{dest}\"";
+        }
 
         await RunCommandAsync(bin, cmd);
 
@@ -153,7 +166,7 @@ internal static class MP4DecryptUtil
     {
         using var fs = File.OpenRead(output);
         var header = new byte[1 * 1024 * 1024]; // 1MB
-        fs.Read(header);
+        _ = fs.Read(header);
         return GetMP4Info(header);
     }