├── .circleci └── config.yml ├── .credo.exs ├── .formatter.exs ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── please--open-new-issues-in-membranefranework-membrane_core.md └── workflows │ ├── on_issue_opened.yaml │ └── on_pr_opened.yaml ├── .gitignore ├── LICENSE ├── README.md ├── lib └── membrane_http_adaptive_stream │ ├── bandwidth_calculator.ex │ ├── hls.ex │ ├── manifest.ex │ ├── manifest │ ├── changeset.ex │ ├── segment.ex │ ├── segment_attribute.ex │ └── track.ex │ ├── sink.ex │ ├── sink_bin.ex │ ├── storage.ex │ └── storages │ ├── file_storage.ex │ ├── genserver_storage.ex │ └── send_storage.ex ├── mix.exs ├── mix.lock └── test ├── membrane_http_adaptive_stream ├── bandwidth_calculator_test.exs ├── integration_test │ ├── fixtures │ │ ├── audio_multiple_video_tracks │ │ │ ├── audio_header_audio_track_part_0.mp4 │ │ │ ├── audio_segment_10_audio_track.m4s │ │ │ ├── audio_segment_11_audio_track.m4s │ │ │ ├── audio_segment_12_audio_track.m4s │ │ │ ├── audio_segment_13_audio_track.m4s │ │ │ ├── audio_segment_14_audio_track.m4s │ │ │ ├── audio_segment_15_audio_track.m4s │ │ │ ├── audio_segment_16_audio_track.m4s │ │ │ ├── audio_segment_1_audio_track.m4s │ │ │ ├── audio_segment_2_audio_track.m4s │ │ │ ├── audio_segment_3_audio_track.m4s │ │ │ ├── audio_segment_4_audio_track.m4s │ │ │ ├── audio_segment_5_audio_track.m4s │ │ │ ├── audio_segment_6_audio_track.m4s │ │ │ ├── audio_segment_7_audio_track.m4s │ │ │ ├── audio_segment_8_audio_track.m4s │ │ │ ├── audio_segment_9_audio_track.m4s │ │ │ ├── audio_track.m3u8 │ │ │ ├── index.m3u8 │ │ │ ├── video_480x270.m3u8 │ │ │ ├── video_540x360.m3u8 │ │ │ ├── video_720x480.m3u8 │ │ │ ├── video_header_video_480x270_part_0.mp4 │ │ │ ├── video_header_video_540x360_part_0.mp4 │ │ │ ├── video_header_video_720x480_part_0.mp4 │ │ │ ├── video_segment_0_video_480x270.m4s │ │ │ ├── video_segment_0_video_540x360.m4s │ │ │ ├── video_segment_0_video_720x480.m4s │ │ │ ├── video_segment_1_video_480x270.m4s │ │ │ ├── video_segment_1_video_540x360.m4s │ │ │ ├── video_segment_1_video_720x480.m4s │ │ │ ├── video_segment_2_video_480x270.m4s │ │ │ ├── video_segment_2_video_540x360.m4s │ │ │ ├── video_segment_2_video_720x480.m4s │ │ │ ├── video_segment_3_video_480x270.m4s │ │ │ ├── video_segment_3_video_540x360.m4s │ │ │ ├── video_segment_3_video_720x480.m4s │ │ │ ├── video_segment_4_video_480x270.m4s │ │ │ ├── video_segment_4_video_540x360.m4s │ │ │ ├── video_segment_4_video_720x480.m4s │ │ │ ├── video_segment_5_video_480x270.m4s │ │ │ ├── video_segment_5_video_540x360.m4s │ │ │ ├── video_segment_5_video_720x480.m4s │ │ │ ├── video_segment_6_video_480x270.m4s │ │ │ ├── video_segment_6_video_540x360.m4s │ │ │ ├── video_segment_6_video_720x480.m4s │ │ │ ├── video_segment_7_video_480x270.m4s │ │ │ ├── video_segment_7_video_540x360.m4s │ │ │ ├── video_segment_7_video_720x480.m4s │ │ │ ├── video_segment_8_video_480x270.m4s │ │ │ ├── video_segment_8_video_540x360.m4s │ │ │ ├── video_segment_8_video_720x480.m4s │ │ │ └── video_segment_9_video_480x270.m4s │ │ ├── audio_track │ │ │ ├── audio_header_audio_track_part_0.mp4 │ │ │ ├── audio_segment_0_audio_track.m4s │ │ │ ├── audio_segment_1_audio_track.m4s │ │ │ ├── audio_segment_2_audio_track.m4s │ │ │ ├── audio_segment_3_audio_track.m4s │ │ │ ├── audio_segment_4_audio_track.m4s │ │ │ ├── audio_segment_5_audio_track.m4s │ │ │ ├── audio_track.m3u8 │ │ │ └── index.m3u8 │ │ ├── audio_video_hevc_tracks │ │ │ ├── audio_header_audio_track_part_0.mp4 │ │ │ ├── audio_segment_0_audio_track.m4s │ │ │ ├── audio_segment_1_audio_track.m4s │ │ │ ├── audio_segment_2_audio_track.m4s │ │ │ ├── audio_segment_3_audio_track.m4s │ │ │ ├── audio_segment_4_audio_track.m4s │ │ │ ├── audio_segment_5_audio_track.m4s │ │ │ ├── audio_track.m3u8 │ │ │ ├── index.m3u8 │ │ │ ├── video_header_video_track_part_0.mp4 │ │ │ ├── video_segment_0_video_track.m4s │ │ │ ├── video_segment_1_video_track.m4s │ │ │ ├── video_segment_2_video_track.m4s │ │ │ ├── video_segment_3_video_track.m4s │ │ │ └── video_track.m3u8 │ │ ├── audio_video_tracks │ │ │ ├── audio_header_audio_track_part_0.mp4 │ │ │ ├── audio_segment_0_audio_track.m4s │ │ │ ├── audio_segment_1_audio_track.m4s │ │ │ ├── audio_segment_2_audio_track.m4s │ │ │ ├── audio_segment_3_audio_track.m4s │ │ │ ├── audio_segment_4_audio_track.m4s │ │ │ ├── audio_segment_5_audio_track.m4s │ │ │ ├── audio_track.m3u8 │ │ │ ├── index.m3u8 │ │ │ ├── video_header_video_track_part_0.mp4 │ │ │ ├── video_segment_0_video_track.m4s │ │ │ ├── video_segment_1_video_track.m4s │ │ │ └── video_track.m3u8 │ │ ├── ffmpeg-testsrc.hevc │ │ ├── live │ │ │ ├── audio_header_audio_track_part_0.mp4 │ │ │ ├── audio_segment_11_audio_track.m4s │ │ │ ├── audio_segment_12_audio_track.m4s │ │ │ ├── audio_segment_13_audio_track.m4s │ │ │ ├── audio_segment_14_audio_track.m4s │ │ │ ├── audio_segment_15_audio_track.m4s │ │ │ ├── audio_segment_16_audio_track.m4s │ │ │ ├── audio_track.m3u8 │ │ │ ├── index.m3u8 │ │ │ ├── video_480x270.m3u8 │ │ │ ├── video_540x360.m3u8 │ │ │ ├── video_720x480.m3u8 │ │ │ ├── video_header_video_480x270_part_0.mp4 │ │ │ ├── video_header_video_540x360_part_0.mp4 │ │ │ ├── video_header_video_720x480_part_0.mp4 │ │ │ ├── video_segment_6_video_480x270.m4s │ │ │ ├── video_segment_6_video_540x360.m4s │ │ │ ├── video_segment_6_video_720x480.m4s │ │ │ ├── video_segment_7_video_480x270.m4s │ │ │ ├── video_segment_7_video_540x360.m4s │ │ │ ├── video_segment_7_video_720x480.m4s │ │ │ ├── video_segment_8_video_480x270.m4s │ │ │ ├── video_segment_8_video_540x360.m4s │ │ │ ├── video_segment_8_video_720x480.m4s │ │ │ └── video_segment_9_video_480x270.m4s │ │ ├── muxed_av │ │ │ ├── index.m3u8 │ │ │ ├── muxed_header_video_480x270_part_0.mp4 │ │ │ ├── muxed_header_video_540x360_part_0.mp4 │ │ │ ├── muxed_header_video_720x480_part_0.mp4 │ │ │ ├── muxed_segment_0_video_480x270.m4s │ │ │ ├── muxed_segment_0_video_540x360.m4s │ │ │ ├── muxed_segment_0_video_720x480.m4s │ │ │ ├── muxed_segment_1_video_480x270.m4s │ │ │ ├── muxed_segment_1_video_540x360.m4s │ │ │ ├── muxed_segment_1_video_720x480.m4s │ │ │ ├── muxed_segment_2_video_480x270.m4s │ │ │ ├── muxed_segment_2_video_540x360.m4s │ │ │ ├── muxed_segment_2_video_720x480.m4s │ │ │ ├── muxed_segment_3_video_480x270.m4s │ │ │ ├── muxed_segment_3_video_540x360.m4s │ │ │ ├── muxed_segment_3_video_720x480.m4s │ │ │ ├── muxed_segment_4_video_480x270.m4s │ │ │ ├── muxed_segment_4_video_540x360.m4s │ │ │ ├── muxed_segment_4_video_720x480.m4s │ │ │ ├── muxed_segment_5_video_480x270.m4s │ │ │ ├── muxed_segment_5_video_540x360.m4s │ │ │ ├── muxed_segment_5_video_720x480.m4s │ │ │ ├── muxed_segment_6_video_480x270.m4s │ │ │ ├── muxed_segment_6_video_540x360.m4s │ │ │ ├── muxed_segment_6_video_720x480.m4s │ │ │ ├── muxed_segment_7_video_480x270.m4s │ │ │ ├── muxed_segment_7_video_540x360.m4s │ │ │ ├── muxed_segment_7_video_720x480.m4s │ │ │ ├── muxed_segment_8_video_480x270.m4s │ │ │ ├── muxed_segment_8_video_540x360.m4s │ │ │ ├── muxed_segment_8_video_720x480.m4s │ │ │ ├── muxed_segment_9_video_480x270.m4s │ │ │ ├── video_480x270.m3u8 │ │ │ ├── video_540x360.m3u8 │ │ │ └── video_720x480.m3u8 │ │ └── persisted │ │ │ ├── audio_header_audio_track_part_0.mp4 │ │ │ ├── audio_segment_0_audio_track.m4s │ │ │ ├── audio_segment_10_audio_track.m4s │ │ │ ├── audio_segment_11_audio_track.m4s │ │ │ ├── audio_segment_12_audio_track.m4s │ │ │ ├── audio_segment_13_audio_track.m4s │ │ │ ├── audio_segment_14_audio_track.m4s │ │ │ ├── audio_segment_15_audio_track.m4s │ │ │ ├── audio_segment_16_audio_track.m4s │ │ │ ├── audio_segment_1_audio_track.m4s │ │ │ ├── audio_segment_2_audio_track.m4s │ │ │ ├── audio_segment_3_audio_track.m4s │ │ │ ├── audio_segment_4_audio_track.m4s │ │ │ ├── audio_segment_5_audio_track.m4s │ │ │ ├── audio_segment_6_audio_track.m4s │ │ │ ├── audio_segment_7_audio_track.m4s │ │ │ ├── audio_segment_8_audio_track.m4s │ │ │ ├── audio_segment_9_audio_track.m4s │ │ │ ├── audio_track.m3u8 │ │ │ ├── index.m3u8 │ │ │ ├── video_480x270.m3u8 │ │ │ ├── video_540x360.m3u8 │ │ │ ├── video_720x480.m3u8 │ │ │ ├── video_header_video_480x270_part_0.mp4 │ │ │ ├── video_header_video_540x360_part_0.mp4 │ │ │ ├── video_header_video_720x480_part_0.mp4 │ │ │ ├── video_segment_0_video_480x270.m4s │ │ │ ├── video_segment_0_video_540x360.m4s │ │ │ ├── video_segment_0_video_720x480.m4s │ │ │ ├── video_segment_1_video_480x270.m4s │ │ │ ├── video_segment_1_video_540x360.m4s │ │ │ ├── video_segment_1_video_720x480.m4s │ │ │ ├── video_segment_2_video_480x270.m4s │ │ │ ├── video_segment_2_video_540x360.m4s │ │ │ ├── video_segment_2_video_720x480.m4s │ │ │ ├── video_segment_3_video_480x270.m4s │ │ │ ├── video_segment_3_video_540x360.m4s │ │ │ ├── video_segment_3_video_720x480.m4s │ │ │ ├── video_segment_4_video_480x270.m4s │ │ │ ├── video_segment_4_video_540x360.m4s │ │ │ ├── video_segment_4_video_720x480.m4s │ │ │ ├── video_segment_5_video_480x270.m4s │ │ │ ├── video_segment_5_video_540x360.m4s │ │ │ ├── video_segment_5_video_720x480.m4s │ │ │ ├── video_segment_6_video_480x270.m4s │ │ │ ├── video_segment_6_video_540x360.m4s │ │ │ ├── video_segment_6_video_720x480.m4s │ │ │ ├── video_segment_7_video_480x270.m4s │ │ │ ├── video_segment_7_video_540x360.m4s │ │ │ ├── video_segment_7_video_720x480.m4s │ │ │ ├── video_segment_8_video_480x270.m4s │ │ │ ├── video_segment_8_video_540x360.m4s │ │ │ ├── video_segment_8_video_720x480.m4s │ │ │ └── video_segment_9_video_480x270.m4s │ └── sink_bin_integration_test.exs └── sink_test.exs └── test_helper.exs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | elixir: membraneframework/elixir@1 4 | 5 | workflows: 6 | version: 2 7 | build: 8 | jobs: 9 | - elixir/build_test: 10 | filters: &filters 11 | tags: 12 | only: /v.*/ 13 | - elixir/test: 14 | filters: 15 | <<: *filters 16 | - elixir/lint: 17 | filters: 18 | <<: *filters 19 | - elixir/hex_publish: 20 | requires: 21 | - elixir/build_test 22 | - elixir/test 23 | - elixir/lint 24 | context: 25 | - Deployment 26 | filters: 27 | branches: 28 | ignore: /.*/ 29 | tags: 30 | only: /v.*/ 31 | -------------------------------------------------------------------------------- /.credo.exs: -------------------------------------------------------------------------------- 1 | # This file contains the configuration for Credo and you are probably reading 2 | # this after creating it with `mix credo.gen.config`. 3 | # 4 | # If you find anything wrong or unclear in this file, please report an 5 | # issue on GitHub: https://github.com/rrrene/credo/issues 6 | # 7 | %{ 8 | # 9 | # You can have as many configs as you like in the `configs:` field. 10 | configs: [ 11 | %{ 12 | # 13 | # Run any config using `mix credo -C `. If no config name is given 14 | # "default" is used. 15 | # 16 | name: "default", 17 | # 18 | # These are the files included in the analysis: 19 | files: %{ 20 | # 21 | # You can give explicit globs or simply directories. 22 | # In the latter case `**/*.{ex,exs}` will be used. 23 | # 24 | included: [ 25 | "lib/", 26 | "src/", 27 | "test/", 28 | "web/", 29 | "apps/*/lib/", 30 | "apps/*/src/", 31 | "apps/*/test/", 32 | "apps/*/web/" 33 | ], 34 | excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] 35 | }, 36 | # 37 | # Load and configure plugins here: 38 | # 39 | plugins: [], 40 | # 41 | # If you create your own checks, you must specify the source files for 42 | # them here, so they can be loaded by Credo before running the analysis. 43 | # 44 | requires: [], 45 | # 46 | # If you want to enforce a style guide and need a more traditional linting 47 | # experience, you can change `strict` to `true` below: 48 | # 49 | strict: false, 50 | # 51 | # To modify the timeout for parsing files, change this value: 52 | # 53 | parse_timeout: 5000, 54 | # 55 | # If you want to use uncolored output by default, you can change `color` 56 | # to `false` below: 57 | # 58 | color: true, 59 | # 60 | # You can customize the parameters of any check by adding a second element 61 | # to the tuple. 62 | # 63 | # To disable a check put `false` as second element: 64 | # 65 | # {Credo.Check.Design.DuplicatedCode, false} 66 | # 67 | checks: [ 68 | # 69 | ## Consistency Checks 70 | # 71 | {Credo.Check.Consistency.ExceptionNames, []}, 72 | {Credo.Check.Consistency.LineEndings, []}, 73 | {Credo.Check.Consistency.ParameterPatternMatching, []}, 74 | {Credo.Check.Consistency.SpaceAroundOperators, []}, 75 | {Credo.Check.Consistency.SpaceInParentheses, []}, 76 | {Credo.Check.Consistency.TabsOrSpaces, []}, 77 | 78 | # 79 | ## Design Checks 80 | # 81 | # You can customize the priority of any check 82 | # Priority values are: `low, normal, high, higher` 83 | # 84 | {Credo.Check.Design.AliasUsage, 85 | [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, 86 | # You can also customize the exit_status of each check. 87 | # If you don't want TODO comments to cause `mix credo` to fail, just 88 | # set this value to 0 (zero). 89 | # 90 | {Credo.Check.Design.TagTODO, [exit_status: 0]}, 91 | {Credo.Check.Design.TagFIXME, []}, 92 | 93 | # 94 | ## Readability Checks 95 | # 96 | {Credo.Check.Readability.AliasOrder, [priority: :normal]}, 97 | {Credo.Check.Readability.FunctionNames, []}, 98 | {Credo.Check.Readability.LargeNumbers, []}, 99 | {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, 100 | {Credo.Check.Readability.ModuleAttributeNames, []}, 101 | {Credo.Check.Readability.ModuleDoc, []}, 102 | {Credo.Check.Readability.ModuleNames, []}, 103 | {Credo.Check.Readability.ParenthesesInCondition, []}, 104 | {Credo.Check.Readability.ParenthesesOnZeroArityDefs, parens: true}, 105 | {Credo.Check.Readability.PredicateFunctionNames, []}, 106 | {Credo.Check.Readability.PreferImplicitTry, []}, 107 | {Credo.Check.Readability.RedundantBlankLines, []}, 108 | {Credo.Check.Readability.Semicolons, []}, 109 | {Credo.Check.Readability.SpaceAfterCommas, []}, 110 | {Credo.Check.Readability.StringSigils, []}, 111 | {Credo.Check.Readability.TrailingBlankLine, []}, 112 | {Credo.Check.Readability.TrailingWhiteSpace, []}, 113 | {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, 114 | {Credo.Check.Readability.VariableNames, []}, 115 | {Credo.Check.Readability.WithSingleClause, false}, 116 | 117 | # 118 | ## Refactoring Opportunities 119 | # 120 | {Credo.Check.Refactor.CondStatements, []}, 121 | {Credo.Check.Refactor.CyclomaticComplexity, []}, 122 | {Credo.Check.Refactor.FunctionArity, []}, 123 | {Credo.Check.Refactor.LongQuoteBlocks, []}, 124 | {Credo.Check.Refactor.MapInto, false}, 125 | {Credo.Check.Refactor.MatchInCondition, []}, 126 | {Credo.Check.Refactor.NegatedConditionsInUnless, []}, 127 | {Credo.Check.Refactor.NegatedConditionsWithElse, []}, 128 | {Credo.Check.Refactor.Nesting, []}, 129 | {Credo.Check.Refactor.UnlessWithElse, []}, 130 | {Credo.Check.Refactor.WithClauses, []}, 131 | 132 | # 133 | ## Warnings 134 | # 135 | {Credo.Check.Warning.BoolOperationOnSameValues, []}, 136 | {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, 137 | {Credo.Check.Warning.IExPry, []}, 138 | {Credo.Check.Warning.IoInspect, []}, 139 | {Credo.Check.Warning.LazyLogging, false}, 140 | {Credo.Check.Warning.MixEnv, []}, 141 | {Credo.Check.Warning.OperationOnSameValues, []}, 142 | {Credo.Check.Warning.OperationWithConstantResult, []}, 143 | {Credo.Check.Warning.RaiseInsideRescue, []}, 144 | {Credo.Check.Warning.UnusedEnumOperation, []}, 145 | {Credo.Check.Warning.UnusedFileOperation, []}, 146 | {Credo.Check.Warning.UnusedKeywordOperation, []}, 147 | {Credo.Check.Warning.UnusedListOperation, []}, 148 | {Credo.Check.Warning.UnusedPathOperation, []}, 149 | {Credo.Check.Warning.UnusedRegexOperation, []}, 150 | {Credo.Check.Warning.UnusedStringOperation, []}, 151 | {Credo.Check.Warning.UnusedTupleOperation, []}, 152 | {Credo.Check.Warning.UnsafeExec, []}, 153 | 154 | # 155 | # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) 156 | 157 | # 158 | # Controversial and experimental checks (opt-in, just replace `false` with `[]`) 159 | # 160 | {Credo.Check.Readability.StrictModuleLayout, 161 | priority: :normal, order: ~w/shortdoc moduledoc behaviour use import require alias/a}, 162 | {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, 163 | {Credo.Check.Consistency.UnusedVariableNames, force: :meaningful}, 164 | {Credo.Check.Design.DuplicatedCode, false}, 165 | {Credo.Check.Readability.AliasAs, false}, 166 | {Credo.Check.Readability.MultiAlias, false}, 167 | {Credo.Check.Readability.Specs, []}, 168 | {Credo.Check.Readability.SinglePipe, false}, 169 | {Credo.Check.Readability.WithCustomTaggedTuple, false}, 170 | {Credo.Check.Refactor.ABCSize, false}, 171 | {Credo.Check.Refactor.AppendSingleItem, false}, 172 | {Credo.Check.Refactor.DoubleBooleanNegation, false}, 173 | {Credo.Check.Refactor.ModuleDependencies, false}, 174 | {Credo.Check.Refactor.NegatedIsNil, false}, 175 | {Credo.Check.Refactor.PipeChainStart, false}, 176 | {Credo.Check.Refactor.VariableRebinding, false}, 177 | {Credo.Check.Warning.LeakyEnvironment, false}, 178 | {Credo.Check.Warning.MapGetUnsafePass, false}, 179 | {Credo.Check.Warning.UnsafeToAtom, false} 180 | 181 | # 182 | # Custom checks can be created using `mix credo.gen.check`. 183 | # 184 | ] 185 | } 186 | ] 187 | } 188 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: [ 3 | "{lib,test,config}/**/*.{ex,exs}", 4 | ".formatter.exs", 5 | "*.exs" 6 | ], 7 | import_deps: [:membrane_core] 8 | ] 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/.github/CODEOWNERS -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/please--open-new-issues-in-membranefranework-membrane_core.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Please, open new issues in membranefranework/membrane_core 3 | about: New issues related to this repo should be opened there 4 | title: "[DO NOT OPEN]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please, do not open this issue here. Open it in the [membrane_core](https://github.com/membraneframework/membrane_core) repository instead. 11 | 12 | Thanks for helping us grow :) 13 | -------------------------------------------------------------------------------- /.github/workflows/on_issue_opened.yaml: -------------------------------------------------------------------------------- 1 | name: 'Close issue when opened' 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | jobs: 7 | close: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout membrane_core 11 | uses: actions/checkout@v3 12 | with: 13 | repository: membraneframework/membrane_core 14 | - name: Close issue 15 | uses: ./.github/actions/close_issue 16 | with: 17 | GITHUB_TOKEN: ${{ secrets.MEMBRANEFRAMEWORKADMIN_TOKEN }} 18 | ISSUE_URL: ${{ github.event.issue.html_url }} 19 | ISSUE_NUMBER: ${{ github.event.issue.number }} 20 | REPOSITORY: ${{ github.repository }} 21 | -------------------------------------------------------------------------------- /.github/workflows/on_pr_opened.yaml: -------------------------------------------------------------------------------- 1 | name: Add PR to Smackore project board, if the author is from outside Membrane Team 2 | on: 3 | pull_request_target: 4 | types: 5 | - opened 6 | jobs: 7 | maybe_add_to_project_board: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout membrane_core 11 | uses: actions/checkout@v3 12 | with: 13 | repository: membraneframework/membrane_core 14 | - name: Puts PR in "New PRs by community" column in the Smackore project, if the author is from outside Membrane Team 15 | uses: ./.github/actions/add_pr_to_smackore_board 16 | with: 17 | GITHUB_TOKEN: ${{ secrets.MEMBRANEFRAMEWORKADMIN_TOKEN }} 18 | AUTHOR_LOGIN: ${{ github.event.pull_request.user.login }} 19 | PR_URL: ${{ github.event.pull_request.html_url }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | compile_commands.json 2 | .gdb_history 3 | bundlex.sh 4 | bundlex.bat 5 | 6 | # Dir generated by tmp_dir ExUnit tag 7 | /tmp/ 8 | 9 | # Created by https://www.gitignore.io/api/c,vim,linux,macos,elixir,windows,visualstudiocode 10 | # Edit at https://www.gitignore.io/?templates=c,vim,linux,macos,elixir,windows,visualstudiocode 11 | 12 | ### C ### 13 | # Prerequisites 14 | *.d 15 | 16 | # Object files 17 | *.o 18 | *.ko 19 | *.obj 20 | *.elf 21 | 22 | # Linker output 23 | *.ilk 24 | *.map 25 | *.exp 26 | 27 | # Precompiled Headers 28 | *.gch 29 | *.pch 30 | 31 | # Libraries 32 | *.lib 33 | *.a 34 | *.la 35 | *.lo 36 | 37 | # Shared objects (inc. Windows DLLs) 38 | *.dll 39 | *.so 40 | *.so.* 41 | *.dylib 42 | 43 | # Executables 44 | *.exe 45 | *.out 46 | *.app 47 | *.i*86 48 | *.x86_64 49 | *.hex 50 | 51 | # Debug files 52 | *.dSYM/ 53 | *.su 54 | *.idb 55 | *.pdb 56 | 57 | # Kernel Module Compile Results 58 | *.mod* 59 | *.cmd 60 | .tmp_versions/ 61 | modules.order 62 | Module.symvers 63 | Mkfile.old 64 | dkms.conf 65 | 66 | ### Elixir ### 67 | /_build 68 | /cover 69 | /deps 70 | /doc 71 | /.fetch 72 | erl_crash.dump 73 | *.ez 74 | *.beam 75 | /config/*.secret.exs 76 | .elixir_ls/ 77 | 78 | ### Elixir Patch ### 79 | 80 | ### Linux ### 81 | *~ 82 | 83 | # temporary files which can be created if a process still has a handle open of a deleted file 84 | .fuse_hidden* 85 | 86 | # KDE directory preferences 87 | .directory 88 | 89 | # Linux trash folder which might appear on any partition or disk 90 | .Trash-* 91 | 92 | # .nfs files are created when an open file is removed but is still being accessed 93 | .nfs* 94 | 95 | ### macOS ### 96 | # General 97 | .DS_Store 98 | .AppleDouble 99 | .LSOverride 100 | 101 | # Icon must end with two \r 102 | Icon 103 | 104 | # Thumbnails 105 | ._* 106 | 107 | # Files that might appear in the root of a volume 108 | .DocumentRevisions-V100 109 | .fseventsd 110 | .Spotlight-V100 111 | .TemporaryItems 112 | .Trashes 113 | .VolumeIcon.icns 114 | .com.apple.timemachine.donotpresent 115 | 116 | # Directories potentially created on remote AFP share 117 | .AppleDB 118 | .AppleDesktop 119 | Network Trash Folder 120 | Temporary Items 121 | .apdisk 122 | 123 | ### Vim ### 124 | # Swap 125 | [._]*.s[a-v][a-z] 126 | [._]*.sw[a-p] 127 | [._]s[a-rt-v][a-z] 128 | [._]ss[a-gi-z] 129 | [._]sw[a-p] 130 | 131 | # Session 132 | Session.vim 133 | Sessionx.vim 134 | 135 | # Temporary 136 | .netrwhist 137 | # Auto-generated tag files 138 | tags 139 | # Persistent undo 140 | [._]*.un~ 141 | 142 | ### VisualStudioCode ### 143 | .vscode/* 144 | !.vscode/settings.json 145 | !.vscode/tasks.json 146 | !.vscode/launch.json 147 | !.vscode/extensions.json 148 | 149 | ### VisualStudioCode Patch ### 150 | # Ignore all local history of files 151 | .history 152 | 153 | ### Windows ### 154 | # Windows thumbnail cache files 155 | Thumbs.db 156 | Thumbs.db:encryptable 157 | ehthumbs.db 158 | ehthumbs_vista.db 159 | 160 | # Dump file 161 | *.stackdump 162 | 163 | # Folder config file 164 | [Dd]esktop.ini 165 | 166 | # Recycle Bin used on file shares 167 | $RECYCLE.BIN/ 168 | 169 | # Windows Installer files 170 | *.cab 171 | *.msi 172 | *.msix 173 | *.msm 174 | *.msp 175 | 176 | # Windows shortcuts 177 | *.lnk 178 | 179 | # asdf 180 | .tool-versions 181 | 182 | # End of https://www.gitignore.io/api/c,vim,linux,macos,elixir,windows,visualstudiocode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Software Mansion 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Membrane HTTP Adaptive Streaming Plugin 2 | 3 | [![Hex.pm](https://img.shields.io/hexpm/v/membrane_http_adaptive_stream_plugin.svg)](https://hex.pm/packages/membrane_http_adaptive_stream_plugin) 4 | [![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](https://hexdocs.pm/membrane_http_adaptive_stream_plugin/) 5 | [![CircleCI](https://circleci.com/gh/membraneframework/membrane_http_adaptive_stream_plugin.svg?style=svg)](https://circleci.com/gh/membraneframework/membrane_http_adaptive_stream_plugin) 6 | 7 | Plugin generating manifests for HTTP adaptive streaming protocols. 8 | Currently, only HTTP Live Streaming (HLS) is supported. 9 | In future, the support for MPEG-DASH is planned as well 10 | 11 | ## Installation 12 | 13 | Add the following line to your `deps` in `mix.exs`. Run `mix deps.get`. 14 | 15 | ```elixir 16 | {:membrane_http_adaptive_stream_plugin, "~> 0.18.7"} 17 | ``` 18 | 19 | ## Usage Example 20 | 21 | See `test/membrane_http_adaptive_stream/integration_test/sink_bin_integration_test.exs` pipeline for details on how to use HLS plugin and generate HLS playlists for example media tracks. 22 | Master and media playlists with related multimedia content that were generated via this pipeline are stored in `test/membrane_http_adaptive_stream/integration_test/fixtures` directory. 23 | 24 | ## Copyright and License 25 | 26 | Copyright 2019, [Software Mansion](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_http_adaptive_stream_plugin) 27 | 28 | [![Software Mansion](https://logo.swmansion.com/logo?color=white&variant=desktop&width=200&tag=membrane-github)](https://swmansion.com/?utm_source=git&utm_medium=readme&utm_campaign=membrane_http_adaptive_stream_plugin) 29 | 30 | Licensed under the [Apache License, Version 2.0](LICENSE) 31 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/bandwidth_calculator.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.BandwidthCalculator do 2 | @moduledoc false 3 | 4 | # Function to calculate multimedia track bandwidth 5 | # For a single track it comes down to finding a single segment with the highest bitrate, equal to the size in bits to duration ratio. 6 | 7 | use Numbers, overload_operators: true 8 | 9 | alias Membrane.HTTPAdaptiveStream.Manifest.Track 10 | alias Membrane.Time 11 | 12 | # Default value that is returned when track bandwidth calculation is impossible. High value is used since it is less 13 | # harmful to overestimate bandwidth and force HLS client to switch to track with lower bandwidth than to underestimate 14 | # and risk that client will use track with actual bandwidth that is beyond its capabilities. 15 | @default_bandwidth 2_560_000 16 | 17 | @spec calculate_max_bandwidth(Track.t(), integer()) :: integer() 18 | def calculate_max_bandwidth(track, segments_number \\ 20) do 19 | segments = 20 | track.segments 21 | |> Enum.to_list() 22 | |> Enum.take(-segments_number) 23 | |> Enum.filter(&Map.get(&1, :complete?, true)) 24 | 25 | if Enum.empty?(segments) do 26 | @default_bandwidth 27 | else 28 | segments 29 | |> Enum.map(fn sg -> 8 * sg.size / (sg.duration / Time.second()) end) 30 | |> Enum.max(&Ratio.gte?/2) 31 | |> Ratio.trunc() 32 | end 33 | end 34 | 35 | @spec calculate_avg_bandwidth(Track.t(), integer()) :: integer() 36 | def calculate_avg_bandwidth(track, segments_number \\ 20) do 37 | segments = 38 | track.segments 39 | |> Enum.to_list() 40 | |> Enum.take(-segments_number) 41 | |> Enum.filter(&Map.get(&1, :complete?, true)) 42 | 43 | if Enum.empty?(segments) do 44 | @default_bandwidth 45 | else 46 | segments 47 | |> Enum.reduce(Ratio.new(0), fn sg, ratio -> 48 | Ratio.new(8 * sg.size * Time.second()) 49 | |> Numbers.div(sg.duration) 50 | |> Numbers.add(ratio) 51 | end) 52 | |> then(&(&1 / Enum.count(segments))) 53 | |> Ratio.trunc() 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/manifest.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.Manifest do 2 | @moduledoc """ 3 | Behaviour for manifest serialization. 4 | """ 5 | use Bunch.Access 6 | 7 | alias __MODULE__.{Changeset, Track} 8 | 9 | @type serialized_manifest_t :: {manifest_name :: String.t(), manifest_content :: String.t()} 10 | 11 | @type serialized_manifests_t :: %{ 12 | master_manifest: serialized_manifest_t(), 13 | manifest_per_track: %{ 14 | optional(track_id :: any()) => serialized_manifest_t() 15 | } 16 | } 17 | 18 | @callback serialize(t) :: serialized_manifests_t() 19 | 20 | @type t :: %__MODULE__{ 21 | name: String.t(), 22 | module: module, 23 | tracks: %{(id :: any) => Track.t()} 24 | } 25 | 26 | @enforce_keys [:name, :module] 27 | defstruct @enforce_keys ++ [tracks: %{}] 28 | 29 | @doc """ 30 | Add a track to the manifest. 31 | 32 | Returns the name under which the header file should be stored. 33 | """ 34 | @spec add_track(t, Track.Config.t()) :: {header_name :: String.t(), t} 35 | def add_track(manifest, %Track.Config{} = config) do 36 | track = Track.new(config) 37 | manifest = %__MODULE__{manifest | tracks: Map.put(manifest.tracks, config.id, track)} 38 | {track.header_name, manifest} 39 | end 40 | 41 | @doc """ 42 | Adds segment to the manifest. In case of ll-hls it will add partial segment, and also full segment if needed. 43 | Returns `Membrane.HTTPAdaptiveStream.Manifest.Track.Changeset`. 44 | """ 45 | @spec add_chunk( 46 | t, 47 | track_id :: Track.id_t(), 48 | Membrane.Buffer.t() 49 | ) :: 50 | {Changeset.t(), t} 51 | def add_chunk(%__MODULE__{} = manifest, track_id, buffer) do 52 | opts = %{ 53 | payload: buffer.payload, 54 | size: byte_size(buffer.payload), 55 | independent?: Map.get(buffer.metadata, :independent?, true), 56 | last_chunk?: Map.fetch!(buffer.metadata, :last_chunk?), 57 | duration: buffer.metadata.duration, 58 | complete?: true 59 | } 60 | 61 | get_and_update_in( 62 | manifest, 63 | [:tracks, track_id], 64 | &Track.add_chunk(&1, opts) 65 | ) 66 | end 67 | 68 | @spec serialize(t) :: serialized_manifests_t() 69 | def serialize(%__MODULE__{module: module} = manifest) do 70 | module.serialize(manifest) 71 | end 72 | 73 | @spec has_track?(t(), Track.id_t()) :: boolean() 74 | def has_track?(%__MODULE__{tracks: tracks}, track_id), do: Map.has_key?(tracks, track_id) 75 | 76 | @spec persisted?(t(), Track.id_t()) :: boolean() 77 | def persisted?(%__MODULE__{tracks: tracks}, track_id), 78 | do: Track.persisted?(Map.get(tracks, track_id)) 79 | 80 | @doc """ 81 | Append a discontinuity to the track. 82 | 83 | This will inform the player that eg. the parameters of the encoder changed and allow you to provide a new MP4 header. 84 | For details on discontinuities refer to [RFC 8216](https://datatracker.ietf.org/doc/html/rfc8216). 85 | """ 86 | @spec discontinue_track(t(), Track.id_t()) :: {header_name :: String.t(), t()} 87 | def discontinue_track(%__MODULE__{} = manifest, track_id) do 88 | get_and_update_in( 89 | manifest, 90 | [:tracks, track_id], 91 | &Track.discontinue/1 92 | ) 93 | end 94 | 95 | @spec finish(t, Track.id_t()) :: {Changeset.t(), t} 96 | def finish(%__MODULE__{} = manifest, track_id) do 97 | get_and_update_in(manifest, [:tracks, track_id], &Track.finish/1) 98 | end 99 | 100 | @doc """ 101 | Filter all tracks that have option `:persisted?` set to true, then 102 | restores all the stale segments in those tracks. 103 | """ 104 | @spec from_beginning(t()) :: t 105 | def from_beginning(%__MODULE__{} = manifest) do 106 | tracks = 107 | manifest.tracks 108 | |> Enum.filter(fn {_track_id, track} -> Track.persisted?(track) end) 109 | |> Map.new(fn {track_id, track} -> {track_id, Track.from_beginning(track)} end) 110 | 111 | %__MODULE__{manifest | tracks: tracks} 112 | end 113 | 114 | @doc """ 115 | Returns all segments grouped by the track id. 116 | """ 117 | @spec segments_per_track(t()) :: %{ 118 | optional(track_id :: term()) => [segment_name :: String.t()] 119 | } 120 | def segments_per_track(%__MODULE__{} = manifest) do 121 | Map.new(manifest.tracks, fn {track_id, track} -> {track_id, Track.all_segments(track)} end) 122 | end 123 | 124 | @doc """ 125 | Returns one header per track 126 | """ 127 | @spec header_per_track(t()) :: %{optional(track_id :: term()) => String.t()} 128 | def header_per_track(%__MODULE__{} = manifest) do 129 | Map.new(manifest.tracks, fn {track_id, track} -> {track_id, Track.header(track)} end) 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/manifest/changeset.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.Manifest.Changeset do 2 | @moduledoc """ 3 | Structure representing changes that has been applied to the track. What element has been added 4 | and what elements are to be removed. 5 | """ 6 | defmodule Segment do 7 | @moduledoc """ 8 | Type used to recognize `to_add` segments in Changeset. 9 | """ 10 | @type t :: %__MODULE__{ 11 | type: :segment | :partial_segment, 12 | duration: Membrane.Time.t() | Ratio.t(), 13 | sequence_number: non_neg_integer(), 14 | name: String.t(), 15 | partial_name: String.t() | nil, 16 | payload: binary(), 17 | independent?: boolean(), 18 | byte_offset: non_neg_integer() | nil 19 | } 20 | @enforce_keys [:type, :duration, :sequence_number, :name, :payload] 21 | defstruct @enforce_keys ++ [independent?: nil, byte_offset: nil, partial_name: nil] 22 | end 23 | 24 | @type element_type_t :: :segment | :header 25 | @type t :: %__MODULE__{ 26 | to_add: [Segment.t()], 27 | to_remove: [{element_type_t(), name :: String.t()}] 28 | } 29 | defstruct to_add: [], 30 | to_remove: [] 31 | 32 | @spec merge(t(), t()) :: t() 33 | def merge(%__MODULE__{to_add: to_add_a, to_remove: to_remove_a}, %__MODULE__{ 34 | to_add: to_add_b, 35 | to_remove: to_remove_b 36 | }), 37 | do: %__MODULE__{to_add: to_add_a ++ to_add_b, to_remove: to_remove_a ++ to_remove_b} 38 | end 39 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/manifest/segment.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.Manifest.Segment do 2 | @moduledoc """ 3 | Structure representing a single manifest segment. 4 | 5 | 6 | It stores the following fields: 7 | * `name` - the segment's name 8 | * `duration` - the segment's total duration 9 | * `size` - the byte size of the segment payload 10 | * `attributes` - the meta attributes associated with the segment 11 | * `type` - decides if the structure is a full segment that can exist on its own 12 | or if it hosts and awaits more partial segments 13 | * `parts` - the partial segments making up the full segment 14 | """ 15 | 16 | alias Membrane.HTTPAdaptiveStream.Manifest.SegmentAttribute 17 | 18 | @enforce_keys [:name, :duration, :size, :attributes] 19 | defstruct @enforce_keys ++ [type: :full, parts: []] 20 | 21 | @type segment_duration_t :: Membrane.Time.t() | Ratio.t() 22 | @typedoc """ 23 | Structure of partial segment. 24 | 25 | Attributes representing a partial segment: 26 | * `independent?` - decides if a segment can be played on its own e.g. starts with a keyframe or is an audio sample 27 | * `duration` - the duration of the partial segment 28 | * `attributes` - the attributes for the particular partial segment 29 | """ 30 | @type partial_segment_t :: %{ 31 | name: String.t(), 32 | independent?: boolean(), 33 | duration: segment_duration_t(), 34 | size: non_neg_integer(), 35 | payload: binary() | nil 36 | } 37 | 38 | @typedoc """ 39 | Determines if segment is full and independent or partial 40 | and consists of several partial segments. 41 | """ 42 | @type type_t :: :full | :partial 43 | 44 | @type t :: %__MODULE__{ 45 | name: String.t(), 46 | duration: segment_duration_t(), 47 | size: non_neg_integer(), 48 | attributes: [SegmentAttribute.t()], 49 | type: type_t(), 50 | parts: [partial_segment_t()] 51 | } 52 | end 53 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/manifest/segment_attribute.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.Manifest.SegmentAttribute do 2 | @moduledoc """ 3 | Definition of Segment Attributes and behaviour for serializing them. 4 | This module should also contain macros for generating different types of attributes 5 | """ 6 | 7 | @type segment_type_t :: :discontinuity | :creation_time | atom() 8 | @type t :: {type :: segment_type_t(), arguments :: any()} 9 | 10 | @doc """ 11 | Callback for serializing a segment attribute to a string. It is required for each implementation of this behavior. 12 | """ 13 | @callback serialize(t()) :: [String.t()] 14 | 15 | @doc """ 16 | Creates a definition of a discontinuity segment attribute. 17 | """ 18 | defmacro discontinuity(header, discontinuity_index), 19 | do: {:discontinuity, {header, discontinuity_index}} 20 | end 21 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/sink.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.Sink do 2 | @moduledoc """ 3 | Sink for generating HTTP streaming manifests. 4 | 5 | Uses `Membrane.HTTPAdaptiveStream.Manifest` for manifest serialization 6 | and `Membrane.HTTPAdaptiveStream.Storage` for saving files. 7 | 8 | ## Notifications 9 | 10 | - `{:track_playable, input_pad_id}` - sent when the first segment of a track is 11 | stored, and thus the track is ready to be played 12 | 13 | ## Examples 14 | 15 | The following configuration: 16 | 17 | %#{inspect(__MODULE__)}{ 18 | manifest_config: %ManifestConfig{name: "manifest", module: Membrane.HTTPAdaptiveStream.HLS} 19 | storage: %Membrane.HTTPAdaptiveStream.Storages.FileStorage{directory: "output"} 20 | } 21 | 22 | will generate a HLS manifest in the `output` directory, playable from 23 | `output/manifest.m3u8` file. 24 | """ 25 | 26 | use Membrane.Sink 27 | 28 | require Membrane.HTTPAdaptiveStream.Manifest.SegmentAttribute 29 | 30 | alias Membrane.CMAF 31 | alias Membrane.HTTPAdaptiveStream.Manifest 32 | alias Membrane.HTTPAdaptiveStream.Storage 33 | 34 | defmodule TrackConfig do 35 | @moduledoc """ 36 | Track configuration. For more information checkout `Membrane.HTTPAdaptiveStream.Manifest.Track.Config` 37 | """ 38 | @type t :: %__MODULE__{ 39 | target_window_duration: Membrane.Time.t() | :infinity, 40 | mode: :live | :vod, 41 | header_naming_fun: (Manifest.Track.t(), counter :: non_neg_integer -> String.t()), 42 | segment_naming_fun: (Manifest.Track.t() -> String.t()), 43 | partial_naming_fun: (String.t(), Keyword.t() -> String.t()), 44 | persist?: boolean() 45 | } 46 | 47 | defstruct target_window_duration: Membrane.Time.seconds(40), 48 | mode: :vod, 49 | header_naming_fun: &Manifest.Track.default_header_naming_fun/2, 50 | segment_naming_fun: &Manifest.Track.default_segment_naming_fun/1, 51 | partial_naming_fun: &Manifest.Track.default_partial_naming_fun/2, 52 | persist?: false 53 | end 54 | 55 | defmodule ManifestConfig do 56 | @moduledoc """ 57 | `Membrane.HTTPAdaptiveStream.Manifest` configuration. 58 | """ 59 | 60 | @typedoc """ 61 | Manifest configuration consists of the following fields: 62 | - `name` - name of the main manifest file. 63 | - `module` - implementation of the `Membrane.HTTPAdaptiveStream.Manifest` behaviour. 64 | """ 65 | @type t() :: %__MODULE__{ 66 | name: String.t(), 67 | module: module() 68 | } 69 | 70 | @enforce_keys [:module] 71 | defstruct @enforce_keys ++ [name: "index"] 72 | end 73 | 74 | def_input_pad :input, 75 | availability: :on_request, 76 | flow_control: :manual, 77 | demand_unit: :buffers, 78 | accepted_format: CMAF.Track, 79 | options: [ 80 | track_name: [ 81 | spec: String.t() | nil, 82 | default: nil, 83 | description: """ 84 | Name that will be used to name the media playlist for the given track, as well as its header and segments files. 85 | It must not contain any URI reserved characters. 86 | """ 87 | ], 88 | segment_duration: [ 89 | spec: Membrane.Time.t(), 90 | description: """ 91 | The minimal duration of media segments produced by this particular track. 92 | 93 | In case of regular paced streams the parameter may not have any impact, but when 94 | partial segments gets used it may decide when regular segments gets finalized and new gets started. 95 | """ 96 | ], 97 | partial_segment_duration: [ 98 | spec: Membrane.Time.t() | nil, 99 | default: nil, 100 | description: """ 101 | The target duration of partial segments. 102 | 103 | When set to nil then the track is not supposed to emit partial segments. 104 | """ 105 | ], 106 | max_framerate: [ 107 | spec: float() | nil, 108 | default: nil, 109 | description: """ 110 | The maximal framerate of video variant. This information is used in master playlist. 111 | 112 | When set to nil then this information won't be added to master playlist. For audio it should be set to nil. 113 | """ 114 | ] 115 | ] 116 | 117 | def_options manifest_config: [ 118 | spec: ManifestConfig.t(), 119 | description: """ 120 | """ 121 | ], 122 | track_config: [ 123 | spec: TrackConfig.t(), 124 | description: """ 125 | """ 126 | ], 127 | storage: [ 128 | spec: Storage.config_t(), 129 | description: """ 130 | Storage configuration. May be one of `Membrane.HTTPAdaptiveStream.Storages.*`. 131 | See `Membrane.HTTPAdaptiveStream.Storage` behaviour. 132 | """ 133 | ], 134 | cleanup_after: [ 135 | spec: nil | Membrane.Time.t(), 136 | default: nil, 137 | description: """ 138 | If not `nil`, time after a storage cleanup function should run. 139 | 140 | The function will remove all manifests and segments stored during the stream. 141 | """ 142 | ] 143 | 144 | @impl true 145 | def handle_init(_ctx, options) do 146 | state = 147 | options 148 | |> Map.from_struct() 149 | |> Map.merge(%{ 150 | storage: Storage.new(options.storage), 151 | manifest: %Manifest{ 152 | name: options.manifest_config.name, 153 | module: options.manifest_config.module 154 | }, 155 | playlist_playable_sent: MapSet.new() 156 | }) 157 | 158 | {[], state} 159 | end 160 | 161 | @impl true 162 | def handle_stream_format( 163 | Pad.ref(:input, track_id) = pad_ref, 164 | %CMAF.Track{} = stream_format, 165 | ctx, 166 | state 167 | ) do 168 | {header_name, manifest} = 169 | if Manifest.has_track?(state.manifest, track_id) do 170 | # Arrival of new stream format for an already existing track indicate that stream parameters have changed. 171 | # According to section 4.3.2.3 of RFC 8216, discontinuity needs to be signaled and new header supplied. 172 | Manifest.discontinue_track(state.manifest, track_id) 173 | else 174 | track_options = ctx.pads[pad_ref].options 175 | track_name = serialize_track_name(track_options[:track_name] || track_id) 176 | 177 | track_config_params = 178 | state.track_config 179 | |> Map.from_struct() 180 | |> Map.merge(%{ 181 | id: track_id, 182 | track_name: track_name, 183 | content_type: stream_format.content_type, 184 | header_extension: ".mp4", 185 | segment_extension: ".m4s", 186 | segment_duration: track_options.segment_duration, 187 | partial_segment_duration: track_options.partial_segment_duration, 188 | encoding: stream_format.codecs, 189 | resolution: stream_format.resolution, 190 | max_framerate: track_options.max_framerate 191 | }) 192 | 193 | track_config = struct!(Manifest.Track.Config, track_config_params) 194 | 195 | Manifest.add_track( 196 | state.manifest, 197 | track_config 198 | ) 199 | end 200 | 201 | case Storage.store_header(state.storage, track_id, header_name, stream_format.header) do 202 | {:ok, storage} -> 203 | {[], %{state | storage: storage, manifest: manifest}} 204 | 205 | {{:error, reason}, _storage} -> 206 | raise "Failed to store the header for track #{inspect(track_id)} due to #{inspect(reason)}" 207 | end 208 | end 209 | 210 | @impl true 211 | def handle_playing(ctx, state) do 212 | demands = ctx.pads |> Map.keys() |> Enum.map(&{:demand, &1}) 213 | {demands, state} 214 | end 215 | 216 | @impl true 217 | def handle_pad_added(pad, %{playback: :playing}, state), do: {[demand: pad], state} 218 | 219 | @impl true 220 | def handle_pad_added(_pad, _ctx, state), do: {[], state} 221 | 222 | @impl true 223 | def handle_buffer(Pad.ref(:input, track_id) = pad, buffer, _ctx, %{storage: storage} = state) do 224 | {changeset, manifest} = Manifest.add_chunk(state.manifest, track_id, buffer) 225 | 226 | with {:ok, storage} <- Storage.apply_track_changeset(storage, track_id, changeset), 227 | {:ok, storage} <- serialize_and_store_manifest(manifest, storage) do 228 | {notify, state} = maybe_notify_playable(track_id, state) 229 | {notify ++ [demand: pad], %{state | manifest: manifest, storage: storage}} 230 | else 231 | {{:error, reason}, _storage} -> 232 | raise "Failed to store a buffer for track #{inspect(track_id)} due to #{inspect(reason)}" 233 | end 234 | end 235 | 236 | @impl true 237 | def handle_end_of_stream( 238 | Pad.ref(:input, track_id), 239 | _ctx, 240 | %{manifest: manifest, storage: storage} = state 241 | ) do 242 | {changeset, manifest} = Manifest.finish(manifest, track_id) 243 | 244 | with {:ok, storage} <- Storage.apply_track_changeset(storage, track_id, changeset), 245 | {:ok, storage} <- serialize_and_store_manifest(manifest, storage) do 246 | storage = Storage.clear_cache(storage) 247 | {[], %{state | storage: storage, manifest: manifest}} 248 | else 249 | {{:error, reason}, _storage} -> 250 | raise "Failed to store the finalized manifest for track #{inspect(track_id)} due to #{inspect(reason)}" 251 | end 252 | end 253 | 254 | @impl true 255 | def handle_terminate_request(ctx, state) do 256 | %{ 257 | manifest: manifest, 258 | storage: storage 259 | } = state 260 | 261 | track_ids = 262 | ctx.pads 263 | |> Map.keys() 264 | |> Enum.map(fn 265 | Pad.ref(:input, track_id) -> track_id 266 | end) 267 | 268 | # prevent storing empty manifest, such situation can happen 269 | # when the sink goes from prepared -> playing -> prepared -> stopped 270 | # and in the meantime no media has flown through input pads 271 | any_track_persisted? = 272 | Enum.any?(track_ids, fn track_id -> 273 | Manifest.has_track?(manifest, track_id) and Manifest.persisted?(manifest, track_id) 274 | end) 275 | 276 | result = 277 | if any_track_persisted? do 278 | {result, storage} = 279 | manifest 280 | |> Manifest.from_beginning() 281 | |> serialize_and_store_manifest(storage) 282 | 283 | {result, %{state | storage: storage}} 284 | else 285 | {:ok, state} 286 | end 287 | 288 | case result do 289 | {:ok, state} -> 290 | :ok = maybe_schedule_cleanup_task(state) 291 | 292 | {[terminate: :normal], state} 293 | 294 | {{:error, reason}, _state} -> 295 | raise "Failed to persist the manifest due to #{inspect(reason)}" 296 | end 297 | end 298 | 299 | defp serialize_track_name(track_id) when is_binary(track_id) do 300 | valid_filename_regex = ~r/^[^\/:*?"<>|]+$/ 301 | 302 | if String.match?(track_id, valid_filename_regex) do 303 | track_id 304 | else 305 | raise "The provided track identifier #{inspect(track_id)} is not a valid filename" 306 | end 307 | end 308 | 309 | defp serialize_track_name(track_id) do 310 | track_id |> :erlang.term_to_binary() |> Base.url_encode64(padding: false) 311 | end 312 | 313 | defp maybe_notify_playable(track_id, %{playlist_playable_sent: playlist_playable_sent} = state) do 314 | if MapSet.member?(playlist_playable_sent, track_id) do 315 | {[], state} 316 | else 317 | {[notify_parent: {:track_playable, track_id}], 318 | %{state | playlist_playable_sent: MapSet.put(playlist_playable_sent, track_id)}} 319 | end 320 | end 321 | 322 | defp serialize_and_store_manifest(manifest, storage) do 323 | serialized_manifest = Manifest.serialize(manifest) 324 | Storage.store_manifests(storage, serialized_manifest) 325 | end 326 | 327 | defp maybe_schedule_cleanup_task(%{cleanup_after: nil}), do: :ok 328 | 329 | defp maybe_schedule_cleanup_task(%{ 330 | manifest: manifest, 331 | storage: storage, 332 | cleanup_after: cleanup_after 333 | }) do 334 | {:ok, _pid} = 335 | Task.start(fn -> 336 | segments_to_remove = Manifest.segments_per_track(manifest) 337 | headers_to_remove = Manifest.header_per_track(manifest) 338 | 339 | timeout = Membrane.Time.as_milliseconds(cleanup_after, :round) 340 | 341 | Process.sleep(timeout) 342 | 343 | # cleanup all data of the secondary playlist and the master one 344 | with {:ok, storage} <- 345 | Storage.clean_all_tracks(storage, segments_to_remove, headers_to_remove), 346 | {:ok, _storage} <- Storage.cleanup(storage, :master, [], nil) do 347 | :ok 348 | else 349 | {{:error, reason}, _storage} -> 350 | raise "Failed to cleanup the storage due to #{inspect(reason)}" 351 | end 352 | end) 353 | 354 | :ok 355 | end 356 | end 357 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/sink_bin.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.SinkBin do 2 | @moduledoc """ 3 | Bin responsible for receiving audio and video streams, performing payloading and CMAF muxing 4 | to eventually store them using provided storage configuration. 5 | 6 | ## Input streams 7 | Parsed H264, H265 or AAC video or audio streams are expected to be connected via the `:input` pad. 8 | The type of stream has to be specified via the pad's `:encoding` option. 9 | 10 | ## Output 11 | Specify one of `Membrane.HTTPAdaptiveStream.Storages` as `:storage` to configure the sink. 12 | """ 13 | use Membrane.Bin 14 | 15 | alias Membrane.{AAC, H264, H265, MP4, Time} 16 | alias Membrane.HTTPAdaptiveStream.{Manifest, Sink, Storage} 17 | 18 | def_options manifest_name: [ 19 | spec: String.t(), 20 | default: "index", 21 | description: "Name of the main manifest file" 22 | ], 23 | manifest_module: [ 24 | spec: module, 25 | description: """ 26 | Implementation of the `Membrane.HTTPAdaptiveStream.Manifest` 27 | behaviour. 28 | """ 29 | ], 30 | storage: [ 31 | spec: Storage.config_t(), 32 | description: """ 33 | Storage configuration. May be one of `Membrane.HTTPAdaptiveStream.Storages.*`. 34 | See `Membrane.HTTPAdaptiveStream.Storage` behaviour. 35 | """ 36 | ], 37 | target_window_duration: [ 38 | spec: Time.t() | :infinity, 39 | default: Time.seconds(40), 40 | inspector: &Time.inspect/1, 41 | description: """ 42 | Manifest duration is kept above that time, while the oldest segments 43 | are removed whenever possible. 44 | """ 45 | ], 46 | persist?: [ 47 | spec: boolean, 48 | default: false, 49 | description: """ 50 | If true, stale segments are removed from the manifest only. Once 51 | playback finishes, they are put back into the manifest. 52 | """ 53 | ], 54 | mode: [ 55 | spec: :live | :vod, 56 | default: :vod, 57 | description: """ 58 | Tells if the session is live or a VOD type of broadcast. It can influence type of metadata 59 | inserted into the playlist's manifest. 60 | """ 61 | ], 62 | hls_mode: [ 63 | spec: :muxed_av | :separate_av, 64 | default: :separate_av, 65 | description: """ 66 | Option defining how the incoming tracks will be handled and how CMAF will be muxed. 67 | 68 | - In `:muxed_av` audio will be added to each video rendition, creating CMAF segments that contain both audio and video. 69 | - In `:separate_av` audio and video tracks will be separate and synchronization will need to be sorted out by the player. 70 | """ 71 | ], 72 | header_naming_fun: [ 73 | spec: (Manifest.Track.t(), counter :: non_neg_integer() -> String.t()), 74 | default: &Manifest.Track.default_header_naming_fun/2, 75 | description: 76 | "A function that generates consequent media header names for a given track" 77 | ], 78 | segment_naming_fun: [ 79 | spec: (Manifest.Track.t() -> String.t()), 80 | default: &Manifest.Track.default_segment_naming_fun/1, 81 | description: 82 | "A function that generates consequent segment names for a given track" 83 | ], 84 | mp4_parameters_in_band?: [ 85 | spec: boolean(), 86 | default: false, 87 | description: """ 88 | Determines whether the parameter type nalus will be removed from the stream. 89 | Inband parameters seem to be legal with MP4, but some players don't respond kindly to them, so use at your own risk. 90 | This parameter should be set to true when discontinuity can occur. For example when resolution can change. 91 | """ 92 | ], 93 | cleanup_after: [ 94 | spec: nil | Time.t(), 95 | default: nil, 96 | description: """ 97 | Time after which a fire-and-forget storage cleanup function should run. 98 | 99 | The function will remove all manifests and segments stored during the stream. 100 | """ 101 | ] 102 | 103 | def_input_pad :input, 104 | accepted_format: 105 | any_of( 106 | Membrane.AAC, 107 | Membrane.H264, 108 | Membrane.H265 109 | ), 110 | availability: :on_request, 111 | options: [ 112 | encoding: [ 113 | spec: :AAC | :H264 | :H265, 114 | description: """ 115 | Encoding type determining which parser will be used for the given stream. 116 | """ 117 | ], 118 | track_name: [ 119 | spec: String.t() | nil, 120 | default: nil, 121 | description: """ 122 | Name that will be used to name the media playlist for the given track, as well as its header and segments files. 123 | It must not contain any URI reserved characters 124 | """ 125 | ], 126 | segment_duration: [ 127 | spec: Membrane.Time.t(), 128 | description: """ 129 | The minimal segment duration of the regular segments. 130 | """ 131 | ], 132 | partial_segment_duration: [ 133 | spec: Membrane.Time.t() | nil, 134 | default: nil, 135 | description: """ 136 | The segment duration of the partial segments. 137 | If not set then the bin won't produce any partial segments. 138 | """ 139 | ], 140 | max_framerate: [ 141 | spec: float() | nil, 142 | default: nil, 143 | description: """ 144 | The maximal framerate of video variant. This information is used in master playlist. 145 | 146 | When set to nil then this information won't be added to master playlist. For audio it should be set to nil. 147 | """ 148 | ] 149 | ] 150 | 151 | @impl true 152 | def handle_init(_ctx, opts) do 153 | structure = [ 154 | child(:sink, %Sink{ 155 | manifest_config: %Sink.ManifestConfig{ 156 | name: opts.manifest_name, 157 | module: opts.manifest_module 158 | }, 159 | track_config: %Sink.TrackConfig{ 160 | target_window_duration: opts.target_window_duration, 161 | persist?: opts.persist?, 162 | header_naming_fun: opts.header_naming_fun, 163 | segment_naming_fun: opts.segment_naming_fun, 164 | mode: opts.mode 165 | }, 166 | storage: opts.storage, 167 | cleanup_after: opts.cleanup_after 168 | }) 169 | ] 170 | 171 | state = %{ 172 | mode: opts.hls_mode, 173 | streams_to_start: 0, 174 | streams_to_end: 0, 175 | mp4_parameters_in_band?: opts.mp4_parameters_in_band? 176 | } 177 | 178 | {[spec: structure], state} 179 | end 180 | 181 | @impl true 182 | def handle_pad_added(pad, ctx, state) do 183 | do_handle_pad_added(pad, ctx.pad_options, ctx, state) 184 | end 185 | 186 | defp do_handle_pad_added(pad, pad_options, ctx, state) 187 | 188 | defp do_handle_pad_added(pad, pad_options, ctx, %{mode: :separate_av} = state) do 189 | Pad.ref(:input, ref) = pad 190 | 191 | spec = 192 | bin_input(pad) 193 | |> child({:parser, ref}, get_parser(pad_options.encoding, state)) 194 | |> child({:cmaf_muxer, ref}, cmaf_child_definiton(pad_options)) 195 | |> via_in(pad, options: track_options(ctx)) 196 | |> get_child(:sink) 197 | 198 | state = increment_streams_counters(state) 199 | {[spec: spec], state} 200 | end 201 | 202 | defp do_handle_pad_added( 203 | pad, 204 | %{encoding: encoding} = pad_options, 205 | ctx, 206 | %{mode: :muxed_av} = state 207 | ) 208 | when encoding in [:H264, :H265] and is_map_key(ctx.children, :audio_tee) do 209 | Pad.ref(:input, ref) = pad 210 | parser = get_parser(encoding, state) 211 | muxer = cmaf_child_definiton(pad_options) 212 | 213 | spec = [ 214 | bin_input(pad) 215 | |> child({:parser, ref}, parser) 216 | |> child({:cmaf_muxer, ref}, muxer) 217 | |> via_in(pad, options: track_options(ctx)) 218 | |> get_child(:sink), 219 | get_child(:audio_tee) 220 | |> get_child({:cmaf_muxer, ref}) 221 | ] 222 | 223 | state = increment_streams_counters(state) 224 | {[spec: spec], state} 225 | end 226 | 227 | defp do_handle_pad_added(_pad, %{encoding: encoding}, _ctx, %{mode: :muxed_av} = state) 228 | when encoding in [:H264, :H265] do 229 | state = increment_streams_counters(state) 230 | {[], state} 231 | end 232 | 233 | defp do_handle_pad_added(pad, %{encoding: :AAC} = pad_options, ctx, %{mode: :muxed_av} = state) do 234 | if count_audio_tracks(ctx) > 1, 235 | do: raise("In :muxed_av mode, only one audio input is accepted") 236 | 237 | postponed_cmaf_muxers = 238 | Map.values(ctx.pads) 239 | |> Enum.filter(&(&1.direction == :input and &1.options[:encoding] in [:H264, :H265])) 240 | |> Enum.flat_map(fn pad_data -> 241 | Pad.ref(:input, cmaf_ref) = pad_data.ref 242 | muxer = cmaf_child_definiton(pad_options) 243 | 244 | [ 245 | bin_input(pad_data.ref) 246 | |> child({:parser, cmaf_ref}, get_parser(pad_data.options.encoding, state)) 247 | |> child({:cmaf_muxer, cmaf_ref}, muxer) 248 | |> via_in(pad, options: track_options(ctx)) 249 | |> get_child(:sink), 250 | get_child(:audio_tee) 251 | |> get_child({:cmaf_muxer, cmaf_ref}) 252 | ] 253 | end) 254 | 255 | Pad.ref(:input, ref) = pad 256 | parser = get_parser(:AAC, state) 257 | 258 | spec = 259 | [ 260 | bin_input(pad) 261 | |> child({:parser, ref}, parser) 262 | |> child(:audio_tee, Membrane.Tee.Parallel) 263 | ] ++ postponed_cmaf_muxers 264 | 265 | {[spec: spec], state} 266 | end 267 | 268 | defp cmaf_child_definiton(pad_options) do 269 | %MP4.Muxer.CMAF{ 270 | segment_min_duration: pad_options.segment_duration, 271 | chunk_target_duration: pad_options.partial_segment_duration 272 | } 273 | end 274 | 275 | defp increment_streams_counters(state) do 276 | state 277 | |> Map.update!(:streams_to_start, &(&1 + 1)) 278 | |> Map.update!(:streams_to_end, &(&1 + 1)) 279 | end 280 | 281 | @impl true 282 | def handle_pad_removed(Pad.ref(:input, ref), ctx, state) do 283 | children = 284 | ([ 285 | {:parser, ref} 286 | ] ++ if(state.mode != :muxed_av, do: [{:cmaf_muxer, ref}], else: [])) 287 | |> Enum.filter(fn child_name -> 288 | child_entry = Map.get(ctx.children, child_name) 289 | child_entry != nil and !child_entry.terminating? 290 | end) 291 | 292 | {[remove_children: children], state} 293 | end 294 | 295 | @impl true 296 | def handle_element_start_of_stream( 297 | :sink, 298 | _pad, 299 | _ctx, 300 | %{streams_to_start: 1} = state 301 | ) do 302 | {[notify_parent: :start_of_stream], %{state | streams_to_start: 0}} 303 | end 304 | 305 | @impl true 306 | def handle_element_start_of_stream(:sink, _pad, _ctx, state) do 307 | {[], Map.update!(state, :streams_to_start, &(&1 - 1))} 308 | end 309 | 310 | @impl true 311 | def handle_element_start_of_stream(_element, _pad, _ctx, state) do 312 | {[], state} 313 | end 314 | 315 | @impl true 316 | def handle_element_end_of_stream(:sink, _pad, _ctx, %{streams_to_end: 1} = state) do 317 | {[notify_parent: :end_of_stream], %{state | streams_to_end: 0}} 318 | end 319 | 320 | @impl true 321 | def handle_element_end_of_stream(:sink, _pad, _ctx, state) do 322 | {[], Map.update!(state, :streams_to_end, &(&1 - 1))} 323 | end 324 | 325 | @impl true 326 | def handle_element_end_of_stream(_element, _pad, _ctx, state) do 327 | {[], state} 328 | end 329 | 330 | @impl true 331 | def handle_child_notification( 332 | {:track_playable, track_info}, 333 | :sink, 334 | _ctx, 335 | state 336 | ) do 337 | # notify about playable just when track becomes available 338 | {[notify_parent: {:track_playable, track_info}], state} 339 | end 340 | 341 | defp track_options(context) do 342 | context.pad_options 343 | |> Map.take([:track_name, :segment_duration, :partial_segment_duration, :max_framerate]) 344 | |> Keyword.new() 345 | end 346 | 347 | defp count_audio_tracks(context), 348 | do: 349 | Enum.count(context.pads, fn {_pad, metadata} -> 350 | metadata.options.encoding == :AAC 351 | end) 352 | 353 | defp get_parser(:AAC, _state), do: %AAC.Parser{output_config: :esds, out_encapsulation: :none} 354 | 355 | defp get_parser(:H264, state), 356 | do: %H264.Parser{ 357 | output_stream_structure: if(state.mp4_parameters_in_band?, do: :avc3, else: :avc1), 358 | repeat_parameter_sets: true 359 | } 360 | 361 | defp get_parser(:H265, state), 362 | do: %H265.Parser{ 363 | output_stream_structure: if(state.mp4_parameters_in_band?, do: :hev1, else: :hvc1), 364 | repeat_parameter_sets: true 365 | } 366 | end 367 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/storage.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.Storage do 2 | @moduledoc """ 3 | Behaviour for storing manifests and stream segments. 4 | """ 5 | use Bunch 6 | use Bunch.Access 7 | 8 | alias Membrane.HTTPAdaptiveStream.Manifest 9 | alias Membrane.HTTPAdaptiveStream.Manifest.Changeset 10 | 11 | @type config_t :: struct 12 | @type state_t :: any 13 | @type callback_result_t :: :ok | {:error, reason :: any} 14 | 15 | @type segment_metadata_t :: %{duration: Membrane.Time.t()} 16 | @type partial_segment_metadata :: %{ 17 | duration: Membrane.Time.t(), 18 | independent: boolean(), 19 | byte_offset: non_neg_integer() 20 | } 21 | @type metadata_t :: segment_metadata_t() | partial_segment_metadata() | %{} 22 | 23 | @typedoc """ 24 | The identifier of a parent that the resource belongs to (the track identifier). 25 | 26 | It can either be a master or secondary playlist (a track playlist). 27 | In case of master playlist the identifier will be `:master` while for tracks it can be an arbitrary value. 28 | """ 29 | @type parent_t :: any() 30 | 31 | @doc """ 32 | Generates the storage state based on the configuration struct. 33 | """ 34 | @callback init(config_t) :: state_t 35 | 36 | @doc """ 37 | Stores the resource on a storage. 38 | 39 | Gets the mode that should be used when writing to a file and type of the resource 40 | """ 41 | @callback store( 42 | parent_id :: parent_t(), 43 | resource_name :: String.t(), 44 | content :: String.t() | binary, 45 | metadata :: metadata_t(), 46 | context :: %{ 47 | type: :manifest | :header | :segment | :partial_segment, 48 | mode: :text | :binary 49 | }, 50 | state_t 51 | ) :: {callback_result_t, state :: state_t} 52 | 53 | @doc """ 54 | Removes the resource. 55 | 56 | In case of removing a segment the storage should make sure to remove all 57 | previous partial segments with the same name. It is the user's responsibility to remember 58 | and distinguish between the partial segments. 59 | """ 60 | @callback remove( 61 | parent_id :: parent_t(), 62 | resource_name :: String.t(), 63 | context :: %{type: :manifest | :header | :segment}, 64 | state_t 65 | ) :: {callback_result_t, state :: state_t} 66 | 67 | @enforce_keys [:storage_impl, :impl_state, :cache_enabled?] 68 | defstruct @enforce_keys ++ [cache: %{}, stored_manifests: MapSet.new()] 69 | 70 | @opaque t :: %__MODULE__{ 71 | storage_impl: module, 72 | impl_state: any, 73 | cache_enabled?: bool, 74 | cache: map, 75 | stored_manifests: MapSet.t() 76 | } 77 | 78 | @doc """ 79 | Initializes the storage. 80 | 81 | Accepts the following options: 82 | - `enable_cache` - if true (default), manifests will be stored only if they've been changed 83 | """ 84 | @spec new(config_t, [{:enable_cache, boolean}]) :: t 85 | def new(%storage_impl{} = storage_config, opts \\ []) do 86 | %__MODULE__{ 87 | storage_impl: storage_impl, 88 | impl_state: storage_impl.init(storage_config), 89 | cache_enabled?: Keyword.get(opts, :enable_cache?, true) 90 | } 91 | end 92 | 93 | @doc """ 94 | Stores serialized manifest files 95 | """ 96 | @spec store_manifests(t, Manifest.serialized_manifests_t()) :: 97 | {callback_result_t, t} 98 | def store_manifests(storage, %{ 99 | master_manifest: master_manifest, 100 | manifest_per_track: manifest_per_track 101 | }) do 102 | manifests = [{:master, master_manifest} | Map.to_list(manifest_per_track)] 103 | Bunch.Enum.try_reduce(manifests, storage, &store_manifest/2) 104 | end 105 | 106 | defp store_manifest({id, {name, manifest}}, storage) do 107 | %__MODULE__{ 108 | storage_impl: storage_impl, 109 | impl_state: impl_state, 110 | cache: cache, 111 | cache_enabled?: cache_enabled?, 112 | stored_manifests: stored_manifests 113 | } = storage 114 | 115 | withl cache: false <- cache[name] == manifest, 116 | store: 117 | {:ok, impl_state} <- 118 | storage_impl.store( 119 | id, 120 | name, 121 | manifest, 122 | %{}, 123 | %{mode: :text, type: :manifest}, 124 | impl_state 125 | ), 126 | do: 127 | storage = %{ 128 | storage 129 | | stored_manifests: MapSet.put(stored_manifests, {id, name}), 130 | impl_state: impl_state 131 | }, 132 | update_cache?: true <- cache_enabled? do 133 | storage = put_in(storage, [:cache, name], manifest) 134 | {:ok, storage} 135 | else 136 | cache: true -> 137 | {:ok, storage} 138 | 139 | store: {{:error, reason}, impl_state} -> 140 | {{:error, reason}, %{storage | impl_state: impl_state}} 141 | 142 | update_cache?: false -> 143 | {:ok, storage} 144 | end 145 | end 146 | 147 | @doc """ 148 | Stores stream header file. 149 | """ 150 | @spec store_header(t, track_id :: term(), name :: String.t(), payload :: binary) :: 151 | {callback_result_t, t} 152 | def store_header(storage, track_id, name, payload) do 153 | %__MODULE__{storage_impl: storage_impl, impl_state: impl_state} = storage 154 | 155 | {result, impl_state} = 156 | storage_impl.store( 157 | track_id, 158 | name, 159 | payload, 160 | %{}, 161 | %{mode: :binary, type: :header}, 162 | impl_state 163 | ) 164 | 165 | {result, %{storage | impl_state: impl_state}} 166 | end 167 | 168 | @doc """ 169 | Stores a new segment and removes stale ones. 170 | """ 171 | @spec apply_track_changeset( 172 | t, 173 | track_id :: term(), 174 | Changeset.t() 175 | ) :: {callback_result_t, t} 176 | def apply_track_changeset(storage, track_id, changeset) do 177 | %__MODULE__{storage_impl: storage_impl, impl_state: impl_state} = storage 178 | %Changeset{to_add: to_add, to_remove: to_remove} = changeset 179 | 180 | grouped = 181 | Enum.group_by( 182 | to_remove, 183 | fn {type, _value} -> type end, 184 | fn {_type, value} -> value end 185 | ) 186 | 187 | segment_names = Map.get(grouped, :segment, []) 188 | header_names = Map.get(grouped, :header, []) 189 | 190 | with {:ok, impl_state} <- 191 | Bunch.Enum.try_reduce( 192 | segment_names, 193 | impl_state, 194 | &storage_impl.remove(track_id, &1, %{type: :segment}, &2) 195 | ), 196 | {:ok, impl_state} <- 197 | Bunch.Enum.try_reduce( 198 | header_names, 199 | impl_state, 200 | &storage_impl.remove(track_id, &1, %{type: :header}, &2) 201 | ) do 202 | {result, impl_state} = 203 | Bunch.Enum.try_reduce(to_add, impl_state, fn segment, impl_state -> 204 | %{ 205 | type: type, 206 | name: name, 207 | partial_name: partial_name, 208 | duration: duration, 209 | sequence_number: sequence_number, 210 | independent?: independent?, 211 | byte_offset: byte_offset, 212 | payload: payload 213 | } = segment 214 | 215 | metadata = %{ 216 | partial_name: partial_name, 217 | duration: duration, 218 | sequence_number: sequence_number, 219 | independent?: independent?, 220 | byte_offset: byte_offset 221 | } 222 | 223 | storage_impl.store( 224 | track_id, 225 | name, 226 | payload, 227 | metadata, 228 | %{mode: :binary, type: type}, 229 | impl_state 230 | ) 231 | end) 232 | 233 | {result, %{storage | impl_state: impl_state}} 234 | end 235 | end 236 | 237 | @doc """ 238 | Removes all segments grouped by track. 239 | """ 240 | @type segments :: [String.t()] 241 | @type header :: String.t() 242 | 243 | @spec clean_all_tracks(t(), %{(id :: any()) => segments()}, %{(id :: any()) => header()}) :: 244 | {callback_result_t, t} 245 | def clean_all_tracks(storage, segments_per_track, header_per_track) do 246 | segments_per_track 247 | |> Enum.map(fn {track_id, segments} -> 248 | {track_id, segments, Map.fetch!(header_per_track, track_id)} 249 | end) 250 | |> Bunch.Enum.try_reduce(storage, fn {track_id, segments, header}, storage -> 251 | cleanup(storage, track_id, segments, header) 252 | end) 253 | end 254 | 255 | @doc """ 256 | Removes all the saved segments and manifest for given id. 257 | """ 258 | @spec cleanup(t, id :: any(), segments :: segments(), header :: header() | nil) :: 259 | {callback_result_t, t} 260 | def cleanup(storage, id, segments, header) do 261 | %__MODULE__{storage_impl: storage_impl, impl_state: impl_state, stored_manifests: manifests} = 262 | storage 263 | 264 | manifest_to_delete = Enum.find(manifests, fn {manifest_id, _name} -> manifest_id == id end) 265 | 266 | {manifest_remove_result, impl_state} = 267 | case manifest_to_delete do 268 | nil -> 269 | :ok 270 | 271 | {manifest_id, manifest_name} -> 272 | storage_impl.remove(manifest_id, manifest_name, %{type: :manifest}, impl_state) 273 | end 274 | 275 | with :ok <- manifest_remove_result, 276 | {:ok, impl_state} <- 277 | Bunch.Enum.try_reduce( 278 | segments, 279 | impl_state, 280 | &storage_impl.remove(id, &1, %{type: :segment}, &2) 281 | ), 282 | {:ok, _impl_state} <- maybe_remove_header(id, header, impl_state, storage_impl) do 283 | {:ok, 284 | %__MODULE__{ 285 | storage 286 | | cache: %{}, 287 | stored_manifests: MapSet.delete(manifests, manifest_to_delete) 288 | }} 289 | else 290 | {error, impl_state} -> {error, %{storage | impl_state: impl_state}} 291 | end 292 | end 293 | 294 | @doc """ 295 | Clears the manifest cache. 296 | """ 297 | @spec clear_cache(t) :: t 298 | def clear_cache(storage) do 299 | %__MODULE__{storage | cache: %{}} 300 | end 301 | 302 | defp maybe_remove_header(_track_id, nil, impl_state, _storage_impl), do: {:ok, impl_state} 303 | 304 | defp maybe_remove_header(track_id, header, impl_state, storage_impl), 305 | do: storage_impl.remove(track_id, header, %{type: :header}, impl_state) 306 | end 307 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/storages/file_storage.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.Storages.FileStorage do 2 | @moduledoc """ 3 | `Membrane.HTTPAdaptiveStream.Storage` implementation that saves the stream to 4 | files locally. 5 | """ 6 | @behaviour Membrane.HTTPAdaptiveStream.Storage 7 | 8 | require Logger 9 | 10 | @enforce_keys [:directory] 11 | defstruct @enforce_keys 12 | 13 | @type t :: %__MODULE__{ 14 | directory: Path.t() 15 | } 16 | 17 | @impl true 18 | def init(%__MODULE__{} = config), do: config 19 | 20 | @impl true 21 | def store( 22 | _parent_id, 23 | _name, 24 | _contents, 25 | _metadata, 26 | %{mode: :binary, type: :partial_segment}, 27 | state 28 | ) do 29 | Logger.warning("File storage does not support LL-HLS. The partial segment is omitted.") 30 | {:ok, state} 31 | end 32 | 33 | @impl true 34 | def store( 35 | _parent_id, 36 | name, 37 | contents, 38 | _metadata, 39 | %{mode: :binary}, 40 | %{directory: location} = state 41 | ) do 42 | {File.write(Path.join(location, name), contents, [:binary]), state} 43 | end 44 | 45 | @impl true 46 | def store(_parent_id, name, contents, _metadata, %{mode: :text}, %{directory: location} = state) do 47 | {File.write(Path.join(location, name), contents), state} 48 | end 49 | 50 | @impl true 51 | def remove(_parent_id, name, _ctx, %__MODULE__{directory: location} = state) do 52 | {File.rm(Path.join(location, name)), state} 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/storages/genserver_storage.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.Storages.GenServerStorage do 2 | @moduledoc """ 3 | `Membrane.HTTPAdaptiveStream.Storage` implementation that issues a call or cast 4 | with a `t:message_t/0` to given destination on each call to store/remove. 5 | """ 6 | 7 | @behaviour Membrane.HTTPAdaptiveStream.Storage 8 | 9 | @enforce_keys [:destination] 10 | defstruct @enforce_keys ++ [method: :call] 11 | 12 | @type t :: %__MODULE__{ 13 | destination: Process.dest(), 14 | method: :call | :cast 15 | } 16 | 17 | @type message_t :: store_t | remove_t 18 | 19 | @type store_t :: 20 | {__MODULE__, :store, 21 | %{ 22 | name: String.t(), 23 | contents: String.t(), 24 | type: :manifest | :header | :segment, 25 | mode: :text | :binary 26 | }} 27 | 28 | @type remove_t :: 29 | {__MODULE__, :remove, %{name: String.t(), type: :manifest | :header | :segment}} 30 | 31 | @impl true 32 | def init(%__MODULE__{} = config) do 33 | method = 34 | case config.method do 35 | :call -> &GenServer.call/2 36 | :cast -> &GenServer.cast/2 37 | end 38 | 39 | %__MODULE__{config | method: method} 40 | end 41 | 42 | @impl true 43 | def store(parent_id, name, contents, metadata, context, impl_state) do 44 | params = 45 | Map.merge(context, %{ 46 | parent_id: parent_id, 47 | name: name, 48 | contents: contents, 49 | metadata: metadata 50 | }) 51 | 52 | {impl_state.method.(impl_state.destination, {__MODULE__, :store, params}), impl_state} 53 | end 54 | 55 | @impl true 56 | def remove(parent_id, name, context, impl_state) do 57 | params = Map.merge(context, %{parent_id: parent_id, name: name}) 58 | {impl_state.method.(impl_state.destination, {__MODULE__, :remove, params}), impl_state} 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/membrane_http_adaptive_stream/storages/send_storage.ex: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.Storages.SendStorage do 2 | @moduledoc """ 3 | `Membrane.HTTPAdaptiveStream.Storage` implementation that sends a `t:message_t/0` 4 | to given destination on each call to store/remove. 5 | """ 6 | 7 | @behaviour Membrane.HTTPAdaptiveStream.Storage 8 | 9 | @enforce_keys [:destination] 10 | defstruct @enforce_keys 11 | 12 | @type t :: %__MODULE__{ 13 | destination: Process.dest() 14 | } 15 | 16 | @type message_t :: store_t | remove_t 17 | 18 | @type store_t :: 19 | {__MODULE__, :store, 20 | %{ 21 | name: String.t(), 22 | contents: String.t(), 23 | type: :manifest | :header | :segment | :partial_segment, 24 | mode: :text | :binary 25 | }} 26 | 27 | @type remove_t :: 28 | {__MODULE__, :remove, %{name: String.t(), type: :manifest | :header | :segment}} 29 | 30 | @impl true 31 | def init(%__MODULE__{} = config), do: config 32 | 33 | @impl true 34 | def store(parent_id, name, contents, metadata, context, %{destination: destination} = state) do 35 | send( 36 | destination, 37 | {__MODULE__, :store, 38 | Map.merge(context, %{ 39 | parent_id: parent_id, 40 | name: name, 41 | contents: contents, 42 | metadata: metadata 43 | })} 44 | ) 45 | 46 | {:ok, state} 47 | end 48 | 49 | @impl true 50 | def remove(parent_id, name, context, %{destination: destination} = state) do 51 | send( 52 | destination, 53 | {__MODULE__, :remove, Map.merge(context, %{parent_id: parent_id, name: name})} 54 | ) 55 | 56 | {:ok, state} 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.MixProject do 2 | use Mix.Project 3 | 4 | @version "0.18.7" 5 | @github_url "https://github.com/membraneframework/membrane_http_adaptive_stream_plugin" 6 | 7 | def project do 8 | [ 9 | app: :membrane_http_adaptive_stream_plugin, 10 | version: @version, 11 | elixir: "~> 1.13", 12 | elixirc_paths: elixirc_paths(Mix.env()), 13 | start_permanent: Mix.env() == :prod, 14 | deps: deps(), 15 | dialyzer: dialyzer(), 16 | 17 | # hex 18 | description: "Membrane plugin for adaptive streaming over HTTP", 19 | package: package(), 20 | 21 | # docs 22 | name: "Membrane HTTP Adaptive Stream plugin", 23 | homepage_url: "https://membrane.stream", 24 | source_url: @github_url, 25 | docs: docs() 26 | ] 27 | end 28 | 29 | def application do 30 | [ 31 | extra_applications: [] 32 | ] 33 | end 34 | 35 | defp elixirc_paths(:test), do: ["lib", "test/support"] 36 | defp elixirc_paths(_), do: ["lib"] 37 | 38 | defp docs do 39 | [ 40 | main: "readme", 41 | extras: ["README.md", "LICENSE"], 42 | source_ref: "v#{@version}", 43 | nest_modules_by_prefix: [Membrane.HTTPAdaptiveStream], 44 | groups_for_modules: [ 45 | Elements: [~r/^Membrane.HTTPAdaptiveStream.Sink/], 46 | HLS: [~r/^Membrane.HTTPAdaptiveStream.HLS/], 47 | Manifest: [~r/^Membrane.HTTPAdaptiveStream.Manifest/], 48 | Storages: [~r/^Membrane.HTTPAdaptiveStream.Storage/] 49 | ] 50 | ] 51 | end 52 | 53 | defp package do 54 | [ 55 | maintainers: ["Membrane Team"], 56 | licenses: ["Apache-2.0"], 57 | links: %{ 58 | "GitHub" => @github_url, 59 | "Membrane Framework Homepage" => "https://membrane.stream" 60 | } 61 | ] 62 | end 63 | 64 | defp deps do 65 | [ 66 | {:membrane_core, "~> 1.0"}, 67 | {:membrane_tee_plugin, "~> 0.12.0"}, 68 | {:membrane_mp4_plugin, "~> 0.35.0"}, 69 | {:membrane_aac_plugin, "~> 0.19.0"}, 70 | {:membrane_h26x_plugin, "~> 0.10.0"}, 71 | {:bunch, "~> 1.6"}, 72 | {:qex, "~> 0.5"}, 73 | {:membrane_hackney_plugin, "~> 0.11.0", only: :test}, 74 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, 75 | {:dialyxir, ">= 0.0.0", only: :dev, runtime: false}, 76 | {:credo, ">= 0.0.0", only: :dev, runtime: false} 77 | ] 78 | end 79 | 80 | defp dialyzer() do 81 | opts = [ 82 | flags: [:error_handling] 83 | ] 84 | 85 | if System.get_env("CI") == "true" do 86 | # Store PLTs in cacheable directory for CI 87 | [plt_local_path: "priv/plts", plt_core_path: "priv/plts"] ++ opts 88 | else 89 | opts 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"}, 3 | "bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"}, 4 | "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, 5 | "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, 6 | "coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"}, 7 | "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, 8 | "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, 9 | "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, 10 | "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, 11 | "ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"}, 12 | "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, 13 | "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, 14 | "heap": {:hex, :heap, "2.0.2", "d98cb178286cfeb5edbcf17785e2d20af73ca57b5a2cf4af584118afbcf917eb", [:mix], [], "hexpm", "ba9ea2fe99eb4bcbd9a8a28eaf71cbcac449ca1d8e71731596aace9028c9d429"}, 15 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, 16 | "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, 17 | "logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"}, 18 | "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, 19 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, 20 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, 21 | "membrane_aac_format": {:hex, :membrane_aac_format, "0.8.0", "515631eabd6e584e0e9af2cea80471fee6246484dbbefc4726c1d93ece8e0838", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}], "hexpm", "a30176a94491033ed32be45e51d509fc70a5ee6e751f12fd6c0d60bd637013f6"}, 22 | "membrane_aac_plugin": {:hex, :membrane_aac_plugin, "0.19.0", "58a15efaaa4f2cc91b968464cfd269244e035efdd983aac2e3ddeb176fcf0585", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "eb7e786e650608ee205f4eebff4c1df3677e545acf09802458f77f64f9942fe9"}, 23 | "membrane_cmaf_format": {:hex, :membrane_cmaf_format, "0.7.1", "9ea858faefdcb181cdfa8001be827c35c5f854e9809ad57d7062cff1f0f703fd", [:mix], [], "hexpm", "3c7b4ed2a986e27f6f336d2f19e9442cb31d93b3142fc024c019572faca54a73"}, 24 | "membrane_core": {:hex, :membrane_core, "1.1.1", "4dcff6e9f3b2ecd4f437c20e201e53957731772c0f15b3005062c41f7f58f500", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3802f3fc071505c59d48792487d9927e803d4edb4039710ffa52cdb60bb0aecc"}, 25 | "membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.2", "650e134c2345d946f930082fac8bac9f5aba785a7817d38a9a9da41ffc56fa92", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "df50c6040004cd7b901cf057bd7e99c875bbbd6ae574efc93b2c753c96f43b9d"}, 26 | "membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"}, 27 | "membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"}, 28 | "membrane_h26x_plugin": {:hex, :membrane_h26x_plugin, "0.10.1", "d7aeb166da55c6573b2178e18caeea290b09fd6f3cca428454085223e81476a0", [:mix], [{:bunch, "~> 1.4", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.0", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}], "hexpm", "9cd63a67ffed0654a932efff34395ded04a05e48d08ea996c93daebf889dac08"}, 29 | "membrane_hackney_plugin": {:hex, :membrane_hackney_plugin, "0.11.0", "54b368333a23394e7cac2f4d6b701bf8c5ee6614670a31f4ebe009b5e691a5c1", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "2b28fd1be3c889d5824d7d985598386c7673828c88f49a91221df3626af8a998"}, 30 | "membrane_mp4_format": {:hex, :membrane_mp4_format, "0.8.0", "8c6e7d68829228117d333b4fbb030e7be829aab49dd8cb047fdc664db1812e6a", [:mix], [], "hexpm", "148dea678a1f82ccfd44dbde6f936d2f21255f496cb45a22cc6eec427f025522"}, 31 | "membrane_mp4_plugin": {:hex, :membrane_mp4_plugin, "0.35.2", "cbedb5272ef1c8f7d9cd3c44f820a90306469b1dc84b8db30ff55bb6195b7cb2", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_cmaf_format, "~> 0.7.0", [hex: :membrane_cmaf_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_mp4_format, "~> 0.8.0", [hex: :membrane_mp4_format, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.1", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}], "hexpm", "8afd4e7779a742dd56c23f1f23053933d1b0b34d397ad368a2f56f995edb2fe0"}, 32 | "membrane_opus_format": {:hex, :membrane_opus_format, "0.3.0", "3804d9916058b7cfa2baa0131a644d8186198d64f52d592ae09e0942513cb4c2", [:mix], [], "hexpm", "8fc89c97be50de23ded15f2050fe603dcce732566fe6fdd15a2de01cb6b81afe"}, 33 | "membrane_tee_plugin": {:hex, :membrane_tee_plugin, "0.12.0", "f94989b4080ef4b7937d74c1a14d3379577c7bd4c6d06e5a2bb41c351ad604d4", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "0d61c9ed5e68e5a75d54200e1c6df5739c0bcb52fee0974183ad72446a179887"}, 34 | "membrane_timestamp_queue": {:hex, :membrane_timestamp_queue, "0.2.2", "1c831b2273d018a6548654aa9f7fa7c4b683f71d96ffe164934ef55f9d11f693", [:mix], [{:heap, "~> 2.0", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7c830e760baaced0988421671cd2c83c7cda8d1bd2b61fd05332711675d1204f"}, 35 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, 36 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, 37 | "mockery": {:hex, :mockery, "2.3.1", "a02fd60b10ac9ed37a7a2ecf6786c1f1dd5c75d2b079a60594b089fba32dc087", [:mix], [], "hexpm", "1d0971d88ebf084e962da3f2cfee16f0ea8e04ff73a7710428500d4500b947fa"}, 38 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, 39 | "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, 40 | "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, 41 | "qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"}, 42 | "ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"}, 43 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, 44 | "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, 45 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, 46 | } 47 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/bandwidth_calculator_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.BandwidthCalculatorTest do 2 | use ExUnit.Case 3 | use Numbers, overload_operators: true, comparison: true 4 | 5 | alias Membrane.HTTPAdaptiveStream.BandwidthCalculator 6 | alias Membrane.HTTPAdaptiveStream.Manifest.Track 7 | alias Membrane.Time 8 | 9 | @default_bandwidth 2_560_000 10 | 11 | describe "Bandwidth calculator calculates correct bandwidth" do 12 | test "bandwidth equal to maximum segment bandwidth" do 13 | segments = [{1, 0.8}, {2, 1}, {1, 1}, {2, 1.3}, {3, 0.25}] 14 | test_track = mock_track(segments, Time.seconds(5)) 15 | 16 | assert BandwidthCalculator.calculate_max_bandwidth(test_track) == 17 | (8 * 3 / (0.25 / Time.second())) |> Ratio.floor() 18 | 19 | assert BandwidthCalculator.calculate_avg_bandwidth(test_track) == 20 | segments 21 | |> Enum.map(fn {num, denom} -> Numbers.div(num * 8, denom / Time.second()) end) 22 | |> Enum.reduce(Ratio.new(0), &Numbers.add(&1, &2)) 23 | |> then(&(&1 / Enum.count(segments))) 24 | |> Ratio.floor() 25 | end 26 | 27 | test "no segments in track" do 28 | test_track = mock_track([], 5) 29 | 30 | assert BandwidthCalculator.calculate_max_bandwidth(test_track) == @default_bandwidth 31 | assert BandwidthCalculator.calculate_avg_bandwidth(test_track) == @default_bandwidth 32 | end 33 | end 34 | 35 | defp mock_segment({size, duration}) do 36 | %{ 37 | name: "mock_segment", 38 | duration: duration, 39 | size: size, 40 | attributes: [] 41 | } 42 | end 43 | 44 | defp mock_track(segments_meta, segment_duration) do 45 | segments = segments_meta |> Enum.map(&mock_segment(&1)) |> Qex.new() 46 | 47 | %Track{ 48 | id: "mock_track", 49 | track_name: "mock_track", 50 | content_type: :video, 51 | header_extension: ".mp4", 52 | segment_extension: ".m4s", 53 | segment_duration: segment_duration, 54 | segments: segments 55 | } 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_header_audio_track_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_header_audio_track_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_10_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_10_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_11_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_11_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_12_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_12_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_13_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_13_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_14_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_14_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_15_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_15_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_16_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_16_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_1_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_1_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_2_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_2_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_3_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_3_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_4_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_4_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_5_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_5_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_6_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_6_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_7_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_7_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_8_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_8_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_9_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_segment_9_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/audio_track.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:2 4 | #EXT-X-MEDIA-SEQUENCE:1 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="audio_header_audio_track_part_0.mp4" 7 | #EXTINF:1.9969161, 8 | audio_segment_1_audio_track.m4s 9 | #EXTINF:1.996916099, 10 | audio_segment_2_audio_track.m4s 11 | #EXTINF:1.9969161, 12 | audio_segment_3_audio_track.m4s 13 | #EXTINF:1.9969161, 14 | audio_segment_4_audio_track.m4s 15 | #EXTINF:1.9969161, 16 | audio_segment_5_audio_track.m4s 17 | #EXTINF:1.996916099, 18 | audio_segment_6_audio_track.m4s 19 | #EXTINF:1.9969161, 20 | audio_segment_7_audio_track.m4s 21 | #EXTINF:1.9969161, 22 | audio_segment_8_audio_track.m4s 23 | #EXTINF:1.9969161, 24 | audio_segment_9_audio_track.m4s 25 | #EXTINF:1.9969161, 26 | audio_segment_10_audio_track.m4s 27 | #EXTINF:1.996916099, 28 | audio_segment_11_audio_track.m4s 29 | #EXTINF:1.9969161, 30 | audio_segment_12_audio_track.m4s 31 | #EXTINF:1.9969161, 32 | audio_segment_13_audio_track.m4s 33 | #EXTINF:1.9969161, 34 | audio_segment_14_audio_track.m4s 35 | #EXTINF:1.996916099, 36 | audio_segment_15_audio_track.m4s 37 | #EXTINF:1.091337868, 38 | audio_segment_16_audio_track.m4s 39 | #EXT-X-ENDLIST 40 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/index.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-INDEPENDENT-SEGMENTS 4 | #EXT-X-MEDIA:TYPE=AUDIO,NAME="audio_default_name",GROUP-ID="audio_default_id",AUTOSELECT=YES,DEFAULT=YES,URI="audio_track.m3u8",CODECS="mp4a.40.2" 5 | #EXT-X-STREAM-INF:BANDWIDTH=1129328,AVERAGE-BANDWIDTH=792115,RESOLUTION=480x270,FRAME-RATE=25,CODECS="avc1.42e015",AUDIO="audio_default_id" 6 | video_480x270.m3u8 7 | #EXT-X-STREAM-INF:BANDWIDTH=622012,AVERAGE-BANDWIDTH=316577,RESOLUTION=540x360,FRAME-RATE=25,CODECS="avc1.640015",AUDIO="audio_default_id" 8 | video_540x360.m3u8 9 | #EXT-X-STREAM-INF:BANDWIDTH=946368,AVERAGE-BANDWIDTH=468003,RESOLUTION=720x480,FRAME-RATE=25,CODECS="avc1.64001e",AUDIO="audio_default_id" 10 | video_720x480.m3u8 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_480x270.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:5 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_480x270_part_0.mp4" 7 | #EXTINF:4.8, 8 | video_segment_0_video_480x270.m4s 9 | #EXTINF:4.28, 10 | video_segment_1_video_480x270.m4s 11 | #EXTINF:2.12, 12 | video_segment_2_video_480x270.m4s 13 | #EXTINF:2.0, 14 | video_segment_3_video_480x270.m4s 15 | #EXTINF:2.8, 16 | video_segment_4_video_480x270.m4s 17 | #EXTINF:2.88, 18 | video_segment_5_video_480x270.m4s 19 | #EXTINF:4.8, 20 | video_segment_6_video_480x270.m4s 21 | #EXTINF:2.32, 22 | video_segment_7_video_480x270.m4s 23 | #EXTINF:3.96, 24 | video_segment_8_video_480x270.m4s 25 | #EXTINF:2.48, 26 | video_segment_9_video_480x270.m4s 27 | #EXT-X-ENDLIST 28 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_540x360.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:10 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_540x360_part_0.mp4" 7 | #EXTINF:9.08, 8 | video_segment_0_video_540x360.m4s 9 | #EXTINF:2.12, 10 | video_segment_1_video_540x360.m4s 11 | #EXTINF:2.0, 12 | video_segment_2_video_540x360.m4s 13 | #EXTINF:2.8, 14 | video_segment_3_video_540x360.m4s 15 | #EXTINF:2.96, 16 | video_segment_4_video_540x360.m4s 17 | #EXTINF:2.0, 18 | video_segment_5_video_540x360.m4s 19 | #EXTINF:3.96, 20 | video_segment_6_video_540x360.m4s 21 | #EXTINF:5.04, 22 | video_segment_7_video_540x360.m4s 23 | #EXTINF:2.48, 24 | video_segment_8_video_540x360.m4s 25 | #EXT-X-ENDLIST 26 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_720x480.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:10 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_720x480_part_0.mp4" 7 | #EXTINF:9.08, 8 | video_segment_0_video_720x480.m4s 9 | #EXTINF:2.12, 10 | video_segment_1_video_720x480.m4s 11 | #EXTINF:2.0, 12 | video_segment_2_video_720x480.m4s 13 | #EXTINF:2.8, 14 | video_segment_3_video_720x480.m4s 15 | #EXTINF:3.0, 16 | video_segment_4_video_720x480.m4s 17 | #EXTINF:2.0, 18 | video_segment_5_video_720x480.m4s 19 | #EXTINF:3.92, 20 | video_segment_6_video_720x480.m4s 21 | #EXTINF:5.04, 22 | video_segment_7_video_720x480.m4s 23 | #EXTINF:2.48, 24 | video_segment_8_video_720x480.m4s 25 | #EXT-X-ENDLIST 26 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_header_video_480x270_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_header_video_480x270_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_header_video_540x360_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_header_video_540x360_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_header_video_720x480_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_header_video_720x480_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_0_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_0_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_0_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_0_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_0_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_0_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_1_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_1_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_1_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_1_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_1_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_1_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_2_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_2_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_2_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_2_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_2_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_2_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_3_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_3_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_3_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_3_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_3_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_3_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_4_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_4_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_4_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_4_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_4_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_4_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_5_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_5_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_5_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_5_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_5_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_5_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_6_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_6_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_6_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_6_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_6_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_6_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_7_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_7_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_7_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_7_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_7_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_7_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_8_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_8_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_8_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_8_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_8_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_8_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_9_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_multiple_video_tracks/video_segment_9_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_header_audio_track_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_header_audio_track_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_0_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_0_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_1_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_1_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_2_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_2_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_3_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_3_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_4_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_4_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_5_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_segment_5_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/audio_track.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:2 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="audio_header_audio_track_part_0.mp4" 7 | #EXTINF:1.9969161, 8 | audio_segment_0_audio_track.m4s 9 | #EXTINF:1.9969161, 10 | audio_segment_1_audio_track.m4s 11 | #EXTINF:1.996916099, 12 | audio_segment_2_audio_track.m4s 13 | #EXTINF:1.9969161, 14 | audio_segment_3_audio_track.m4s 15 | #EXTINF:1.9969161, 16 | audio_segment_4_audio_track.m4s 17 | #EXTINF:0.04643991, 18 | audio_segment_5_audio_track.m4s 19 | #EXT-X-ENDLIST 20 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_track/index.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-INDEPENDENT-SEGMENTS 4 | #EXT-X-MEDIA:TYPE=AUDIO,NAME="audio_default_name",GROUP-ID="audio_default_id",AUTOSELECT=YES,DEFAULT=YES,URI="audio_track.m3u8",CODECS="mp4a.40.2" 5 | #EXT-X-STREAM-INF:BANDWIDTH=127821,AVERAGE-BANDWIDTH=88442,CODECS="mp4a.40.2" 6 | audio_track.m3u8 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_header_audio_track_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_header_audio_track_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_0_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_0_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_1_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_1_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_2_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_2_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_3_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_3_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_4_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_4_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_5_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_segment_5_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/audio_track.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:2 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="audio_header_audio_track_part_0.mp4" 7 | #EXTINF:1.9969161, 8 | audio_segment_0_audio_track.m4s 9 | #EXTINF:1.9969161, 10 | audio_segment_1_audio_track.m4s 11 | #EXTINF:1.996916099, 12 | audio_segment_2_audio_track.m4s 13 | #EXTINF:1.9969161, 14 | audio_segment_3_audio_track.m4s 15 | #EXTINF:1.9969161, 16 | audio_segment_4_audio_track.m4s 17 | #EXTINF:0.04643991, 18 | audio_segment_5_audio_track.m4s 19 | #EXT-X-ENDLIST 20 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/index.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-INDEPENDENT-SEGMENTS 4 | #EXT-X-MEDIA:TYPE=AUDIO,NAME="audio_default_name",GROUP-ID="audio_default_id",AUTOSELECT=YES,DEFAULT=YES,URI="audio_track.m3u8",CODECS="mp4a.40.2" 5 | #EXT-X-STREAM-INF:BANDWIDTH=136392,AVERAGE-BANDWIDTH=108637,RESOLUTION=1280x720,FRAME-RATE=25,CODECS="hvc1.1.4.L93.B0",AUDIO="audio_default_id" 6 | video_track.m3u8 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_header_video_track_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_header_video_track_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_segment_0_video_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_segment_0_video_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_segment_1_video_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_segment_1_video_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_segment_2_video_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_segment_2_video_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_segment_3_video_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_segment_3_video_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_hevc_tracks/video_track.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:3 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_track_part_0.mp4" 7 | #EXTINF:3.0, 8 | video_segment_0_video_track.m4s 9 | #EXTINF:3.0, 10 | video_segment_1_video_track.m4s 11 | #EXTINF:3.0, 12 | video_segment_2_video_track.m4s 13 | #EXTINF:1.08, 14 | video_segment_3_video_track.m4s 15 | #EXT-X-ENDLIST 16 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_header_audio_track_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_header_audio_track_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_0_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_0_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_1_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_1_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_2_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_2_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_3_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_3_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_4_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_4_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_5_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_segment_5_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/audio_track.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:2 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="audio_header_audio_track_part_0.mp4" 7 | #EXTINF:1.9969161, 8 | audio_segment_0_audio_track.m4s 9 | #EXTINF:1.9969161, 10 | audio_segment_1_audio_track.m4s 11 | #EXTINF:1.996916099, 12 | audio_segment_2_audio_track.m4s 13 | #EXTINF:1.9969161, 14 | audio_segment_3_audio_track.m4s 15 | #EXTINF:1.9969161, 16 | audio_segment_4_audio_track.m4s 17 | #EXTINF:0.04643991, 18 | audio_segment_5_audio_track.m4s 19 | #EXT-X-ENDLIST 20 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/index.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-INDEPENDENT-SEGMENTS 4 | #EXT-X-MEDIA:TYPE=AUDIO,NAME="audio_default_name",GROUP-ID="audio_default_id",AUTOSELECT=YES,DEFAULT=YES,URI="audio_track.m3u8",CODECS="mp4a.40.2" 5 | #EXT-X-STREAM-INF:BANDWIDTH=121788,AVERAGE-BANDWIDTH=108733,RESOLUTION=1280x720,FRAME-RATE=25,CODECS="avc1.64001f",AUDIO="audio_default_id" 6 | video_track.m3u8 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/video_header_video_track_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/video_header_video_track_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/video_segment_0_video_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/video_segment_0_video_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/video_segment_1_video_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/video_segment_1_video_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/audio_video_tracks/video_track.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:10 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_track_part_0.mp4" 7 | #EXTINF:10.0, 8 | video_segment_0_video_track.m4s 9 | #EXTINF:2.0, 10 | video_segment_1_video_track.m4s 11 | #EXT-X-ENDLIST 12 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/ffmpeg-testsrc.hevc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/ffmpeg-testsrc.hevc -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_header_audio_track_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_header_audio_track_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_11_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_11_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_12_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_12_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_13_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_13_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_14_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_14_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_15_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_15_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_16_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_segment_16_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/audio_track.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:2 4 | #EXT-X-MEDIA-SEQUENCE:11 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="audio_header_audio_track_part_0.mp4" 7 | #EXTINF:1.996916099, 8 | audio_segment_11_audio_track.m4s 9 | #EXTINF:1.9969161, 10 | audio_segment_12_audio_track.m4s 11 | #EXTINF:1.9969161, 12 | audio_segment_13_audio_track.m4s 13 | #EXTINF:1.9969161, 14 | audio_segment_14_audio_track.m4s 15 | #EXTINF:1.996916099, 16 | audio_segment_15_audio_track.m4s 17 | #EXTINF:1.091337868, 18 | audio_segment_16_audio_track.m4s 19 | #EXT-X-ENDLIST 20 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/index.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-INDEPENDENT-SEGMENTS 4 | #EXT-X-MEDIA:TYPE=AUDIO,NAME="audio_default_name",GROUP-ID="audio_default_id",AUTOSELECT=YES,DEFAULT=YES,URI="audio_track.m3u8",CODECS="mp4a.40.2" 5 | #EXT-X-STREAM-INF:BANDWIDTH=1047023,AVERAGE-BANDWIDTH=855807,RESOLUTION=480x270,FRAME-RATE=25,CODECS="avc1.42e015",AUDIO="audio_default_id" 6 | video_480x270.m3u8 7 | #EXT-X-STREAM-INF:BANDWIDTH=395260,AVERAGE-BANDWIDTH=331581,RESOLUTION=540x360,FRAME-RATE=25,CODECS="avc1.640015",AUDIO="audio_default_id" 8 | video_540x360.m3u8 9 | #EXT-X-STREAM-INF:BANDWIDTH=612714,AVERAGE-BANDWIDTH=495249,RESOLUTION=720x480,FRAME-RATE=25,CODECS="avc1.64001e",AUDIO="audio_default_id" 10 | video_720x480.m3u8 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_480x270.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:5 4 | #EXT-X-MEDIA-SEQUENCE:6 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_480x270_part_0.mp4" 7 | #EXTINF:4.8, 8 | video_segment_6_video_480x270.m4s 9 | #EXTINF:2.32, 10 | video_segment_7_video_480x270.m4s 11 | #EXTINF:3.96, 12 | video_segment_8_video_480x270.m4s 13 | #EXTINF:2.48, 14 | video_segment_9_video_480x270.m4s 15 | #EXT-X-ENDLIST 16 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_540x360.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:10 4 | #EXT-X-MEDIA-SEQUENCE:6 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_540x360_part_0.mp4" 7 | #EXTINF:3.96, 8 | video_segment_6_video_540x360.m4s 9 | #EXTINF:5.04, 10 | video_segment_7_video_540x360.m4s 11 | #EXTINF:2.48, 12 | video_segment_8_video_540x360.m4s 13 | #EXT-X-ENDLIST 14 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_720x480.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:10 4 | #EXT-X-MEDIA-SEQUENCE:6 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_720x480_part_0.mp4" 7 | #EXTINF:3.92, 8 | video_segment_6_video_720x480.m4s 9 | #EXTINF:5.04, 10 | video_segment_7_video_720x480.m4s 11 | #EXTINF:2.48, 12 | video_segment_8_video_720x480.m4s 13 | #EXT-X-ENDLIST 14 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_header_video_480x270_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_header_video_480x270_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_header_video_540x360_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_header_video_540x360_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_header_video_720x480_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_header_video_720x480_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_6_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_6_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_6_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_6_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_6_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_6_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_7_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_7_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_7_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_7_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_7_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_7_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_8_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_8_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_8_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_8_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_8_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_8_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_9_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/live/video_segment_9_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/index.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-INDEPENDENT-SEGMENTS 4 | #EXT-X-STREAM-INF:BANDWIDTH=1252874,AVERAGE-BANDWIDTH=917273,RESOLUTION=480x270,FRAME-RATE=25,CODECS="avc1.42e015,mp4a.40.2" 5 | video_480x270.m3u8 6 | #EXT-X-STREAM-INF:BANDWIDTH=763588,AVERAGE-BANDWIDTH=447929,RESOLUTION=540x360,FRAME-RATE=25,CODECS="avc1.640015,mp4a.40.2" 7 | video_540x360.m3u8 8 | #EXT-X-STREAM-INF:BANDWIDTH=1087782,AVERAGE-BANDWIDTH=597835,RESOLUTION=720x480,FRAME-RATE=25,CODECS="avc1.64001e,mp4a.40.2" 9 | video_720x480.m3u8 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_header_video_480x270_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_header_video_480x270_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_header_video_540x360_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_header_video_540x360_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_header_video_720x480_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_header_video_720x480_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_0_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_0_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_0_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_0_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_0_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_0_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_1_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_1_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_1_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_1_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_1_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_1_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_2_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_2_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_2_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_2_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_2_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_2_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_3_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_3_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_3_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_3_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_3_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_3_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_4_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_4_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_4_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_4_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_4_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_4_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_5_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_5_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_5_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_5_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_5_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_5_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_6_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_6_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_6_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_6_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_6_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_6_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_7_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_7_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_7_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_7_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_7_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_7_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_8_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_8_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_8_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_8_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_8_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_8_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_9_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/muxed_segment_9_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/video_480x270.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:5 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="muxed_header_video_480x270_part_0.mp4" 7 | #EXTINF:4.803265306, 8 | muxed_segment_0_video_480x270.m4s 9 | #EXTINF:4.287845805, 10 | muxed_segment_1_video_480x270.m4s 11 | #EXTINF:2.116507936, 12 | muxed_segment_2_video_480x270.m4s 13 | #EXTINF:1.99845805, 14 | muxed_segment_3_video_480x270.m4s 15 | #EXTINF:2.804807256, 16 | muxed_segment_4_video_480x270.m4s 17 | #EXTINF:2.879637188, 18 | muxed_segment_5_video_480x270.m4s 19 | #EXTINF:4.791655328, 20 | muxed_segment_6_video_480x270.m4s 21 | #EXTINF:2.320997732, 22 | muxed_segment_7_video_480x270.m4s 23 | #EXTINF:3.965306122, 24 | muxed_segment_8_video_480x270.m4s 25 | #EXTINF:2.772517006, 26 | muxed_segment_9_video_480x270.m4s 27 | #EXT-X-ENDLIST 28 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/video_540x360.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:9 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="muxed_header_video_540x360_part_0.mp4" 7 | #EXTINF:8.7892517, 8 | muxed_segment_0_video_540x360.m4s 9 | #EXTINF:2.116507936, 10 | muxed_segment_1_video_540x360.m4s 11 | #EXTINF:1.99845805, 12 | muxed_segment_2_video_540x360.m4s 13 | #EXTINF:2.804807256, 14 | muxed_segment_3_video_540x360.m4s 15 | #EXTINF:2.95446712, 16 | muxed_segment_4_video_540x360.m4s 17 | #EXTINF:1.99845805, 18 | muxed_segment_5_video_540x360.m4s 19 | #EXTINF:3.965306122, 20 | muxed_segment_6_video_540x360.m4s 21 | #EXTINF:5.039365079, 22 | muxed_segment_7_video_540x360.m4s 23 | #EXTINF:3.074376417, 24 | muxed_segment_8_video_540x360.m4s 25 | #EXT-X-ENDLIST 26 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/muxed_av/video_720x480.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:9 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="muxed_header_video_720x480_part_0.mp4" 7 | #EXTINF:8.7892517, 8 | muxed_segment_0_video_720x480.m4s 9 | #EXTINF:2.116507936, 10 | muxed_segment_1_video_720x480.m4s 11 | #EXTINF:1.99845805, 12 | muxed_segment_2_video_720x480.m4s 13 | #EXTINF:2.804807256, 14 | muxed_segment_3_video_720x480.m4s 15 | #EXTINF:2.997687074, 16 | muxed_segment_4_video_720x480.m4s 17 | #EXTINF:1.99845805, 18 | muxed_segment_5_video_720x480.m4s 19 | #EXTINF:3.922086168, 20 | muxed_segment_6_video_720x480.m4s 21 | #EXTINF:5.039365079, 22 | muxed_segment_7_video_720x480.m4s 23 | #EXTINF:3.074376417, 24 | muxed_segment_8_video_720x480.m4s 25 | #EXT-X-ENDLIST 26 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_header_audio_track_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_header_audio_track_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_0_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_0_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_10_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_10_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_11_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_11_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_12_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_12_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_13_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_13_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_14_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_14_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_15_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_15_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_16_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_16_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_1_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_1_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_2_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_2_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_3_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_3_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_4_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_4_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_5_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_5_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_6_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_6_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_7_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_7_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_8_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_8_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_9_audio_track.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_segment_9_audio_track.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/audio_track.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:2 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="audio_header_audio_track_part_0.mp4" 7 | #EXTINF:1.9969161, 8 | audio_segment_0_audio_track.m4s 9 | #EXTINF:1.9969161, 10 | audio_segment_1_audio_track.m4s 11 | #EXTINF:1.996916099, 12 | audio_segment_2_audio_track.m4s 13 | #EXTINF:1.9969161, 14 | audio_segment_3_audio_track.m4s 15 | #EXTINF:1.9969161, 16 | audio_segment_4_audio_track.m4s 17 | #EXTINF:1.9969161, 18 | audio_segment_5_audio_track.m4s 19 | #EXTINF:1.996916099, 20 | audio_segment_6_audio_track.m4s 21 | #EXTINF:1.9969161, 22 | audio_segment_7_audio_track.m4s 23 | #EXTINF:1.9969161, 24 | audio_segment_8_audio_track.m4s 25 | #EXTINF:1.9969161, 26 | audio_segment_9_audio_track.m4s 27 | #EXTINF:1.9969161, 28 | audio_segment_10_audio_track.m4s 29 | #EXTINF:1.996916099, 30 | audio_segment_11_audio_track.m4s 31 | #EXTINF:1.9969161, 32 | audio_segment_12_audio_track.m4s 33 | #EXTINF:1.9969161, 34 | audio_segment_13_audio_track.m4s 35 | #EXTINF:1.9969161, 36 | audio_segment_14_audio_track.m4s 37 | #EXTINF:1.996916099, 38 | audio_segment_15_audio_track.m4s 39 | #EXTINF:1.091337868, 40 | audio_segment_16_audio_track.m4s 41 | #EXT-X-ENDLIST 42 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/index.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-INDEPENDENT-SEGMENTS 4 | #EXT-X-MEDIA:TYPE=AUDIO,NAME="audio_default_name",GROUP-ID="audio_default_id",AUTOSELECT=YES,DEFAULT=YES,URI="audio_track.m3u8",CODECS="mp4a.40.2" 5 | #EXT-X-STREAM-INF:BANDWIDTH=1129328,AVERAGE-BANDWIDTH=792115,RESOLUTION=480x270,FRAME-RATE=25,CODECS="avc1.42e015",AUDIO="audio_default_id" 6 | video_480x270.m3u8 7 | #EXT-X-STREAM-INF:BANDWIDTH=622012,AVERAGE-BANDWIDTH=316577,RESOLUTION=540x360,FRAME-RATE=25,CODECS="avc1.640015",AUDIO="audio_default_id" 8 | video_540x360.m3u8 9 | #EXT-X-STREAM-INF:BANDWIDTH=946368,AVERAGE-BANDWIDTH=468003,RESOLUTION=720x480,FRAME-RATE=25,CODECS="avc1.64001e",AUDIO="audio_default_id" 10 | video_720x480.m3u8 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_480x270.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:5 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_480x270_part_0.mp4" 7 | #EXTINF:4.8, 8 | video_segment_0_video_480x270.m4s 9 | #EXTINF:4.28, 10 | video_segment_1_video_480x270.m4s 11 | #EXTINF:2.12, 12 | video_segment_2_video_480x270.m4s 13 | #EXTINF:2.0, 14 | video_segment_3_video_480x270.m4s 15 | #EXTINF:2.8, 16 | video_segment_4_video_480x270.m4s 17 | #EXTINF:2.88, 18 | video_segment_5_video_480x270.m4s 19 | #EXTINF:4.8, 20 | video_segment_6_video_480x270.m4s 21 | #EXTINF:2.32, 22 | video_segment_7_video_480x270.m4s 23 | #EXTINF:3.96, 24 | video_segment_8_video_480x270.m4s 25 | #EXTINF:2.48, 26 | video_segment_9_video_480x270.m4s 27 | #EXT-X-ENDLIST 28 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_540x360.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:10 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_540x360_part_0.mp4" 7 | #EXTINF:9.08, 8 | video_segment_0_video_540x360.m4s 9 | #EXTINF:2.12, 10 | video_segment_1_video_540x360.m4s 11 | #EXTINF:2.0, 12 | video_segment_2_video_540x360.m4s 13 | #EXTINF:2.8, 14 | video_segment_3_video_540x360.m4s 15 | #EXTINF:2.96, 16 | video_segment_4_video_540x360.m4s 17 | #EXTINF:2.0, 18 | video_segment_5_video_540x360.m4s 19 | #EXTINF:3.96, 20 | video_segment_6_video_540x360.m4s 21 | #EXTINF:5.04, 22 | video_segment_7_video_540x360.m4s 23 | #EXTINF:2.48, 24 | video_segment_8_video_540x360.m4s 25 | #EXT-X-ENDLIST 26 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_720x480.m3u8: -------------------------------------------------------------------------------- 1 | #EXTM3U 2 | #EXT-X-VERSION:7 3 | #EXT-X-TARGETDURATION:10 4 | #EXT-X-MEDIA-SEQUENCE:0 5 | #EXT-X-DISCONTINUITY-SEQUENCE:0 6 | #EXT-X-MAP:URI="video_header_video_720x480_part_0.mp4" 7 | #EXTINF:9.08, 8 | video_segment_0_video_720x480.m4s 9 | #EXTINF:2.12, 10 | video_segment_1_video_720x480.m4s 11 | #EXTINF:2.0, 12 | video_segment_2_video_720x480.m4s 13 | #EXTINF:2.8, 14 | video_segment_3_video_720x480.m4s 15 | #EXTINF:3.0, 16 | video_segment_4_video_720x480.m4s 17 | #EXTINF:2.0, 18 | video_segment_5_video_720x480.m4s 19 | #EXTINF:3.92, 20 | video_segment_6_video_720x480.m4s 21 | #EXTINF:5.04, 22 | video_segment_7_video_720x480.m4s 23 | #EXTINF:2.48, 24 | video_segment_8_video_720x480.m4s 25 | #EXT-X-ENDLIST 26 | -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_header_video_480x270_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_header_video_480x270_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_header_video_540x360_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_header_video_540x360_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_header_video_720x480_part_0.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_header_video_720x480_part_0.mp4 -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_0_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_0_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_0_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_0_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_0_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_0_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_1_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_1_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_1_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_1_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_1_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_1_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_2_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_2_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_2_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_2_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_2_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_2_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_3_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_3_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_3_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_3_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_3_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_3_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_4_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_4_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_4_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_4_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_4_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_4_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_5_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_5_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_5_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_5_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_5_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_5_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_6_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_6_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_6_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_6_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_6_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_6_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_7_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_7_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_7_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_7_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_7_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_7_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_8_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_8_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_8_video_540x360.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_8_video_540x360.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_8_video_720x480.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_8_video_720x480.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_9_video_480x270.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/membraneframework/membrane_http_adaptive_stream_plugin/9f0237c2325c4ae4a902e0ba5c6a6acedaf66737/test/membrane_http_adaptive_stream/integration_test/fixtures/persisted/video_segment_9_video_480x270.m4s -------------------------------------------------------------------------------- /test/membrane_http_adaptive_stream/sink_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Membrane.HTTPAdaptiveStream.SinkTest do 2 | use ExUnit.Case, async: true 3 | 4 | import Membrane.Testing.Assertions 5 | 6 | require Membrane.Pad 7 | 8 | alias Membrane.{Buffer, Pad, Testing, Time} 9 | alias Membrane.HTTPAdaptiveStream.Manifest.Track 10 | alias Membrane.HTTPAdaptiveStream.Sink 11 | alias Membrane.HTTPAdaptiveStream.Storages.SendStorage 12 | 13 | defmodule Source do 14 | @moduledoc """ 15 | Trival source to test audio and multiple video track recognition 16 | """ 17 | 18 | use Membrane.Source 19 | alias Membrane.CMAF.Track 20 | 21 | def_output_pad :output, accepted_format: Track, flow_control: :push 22 | 23 | def_options content_type: [spec: :audio | :video], source_id: [spec: String.t()] 24 | 25 | @impl true 26 | def handle_playing(_ctx, state) do 27 | stream_format = %Track{content_type: state.content_type, header: <<>>} 28 | {[stream_format: {:output, stream_format}], state} 29 | end 30 | 31 | @impl true 32 | def handle_parent_notification({:buffer, buffer}, _ctx, state) do 33 | {[buffer: {:output, buffer}], state} 34 | end 35 | end 36 | 37 | test "single track" do 38 | pipeline = mk_pipeline([{:audio, "audio_track"}]) 39 | assert_receive {SendStorage, :store, %{type: :header}} 40 | 41 | send_buf(pipeline, "audio_track", 2) 42 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 43 | assert_receive {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 44 | assert_receive {SendStorage, :store, %{name: "audio_segment_0_" <> _}} 45 | assert_pipeline_notified(pipeline, :sink, {:track_playable, "audio_track"}) 46 | 47 | send_buf(pipeline, "audio_track", 4) 48 | assert_receive {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 49 | assert_receive {SendStorage, :store, %{name: "audio_segment_1_" <> _}} 50 | 51 | send_buf(pipeline, "audio_track", 2) 52 | assert_receive {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 53 | assert_receive {SendStorage, :store, %{name: "audio_segment_2_" <> _}} 54 | assert_receive {SendStorage, :remove, %{name: "audio_segment_0_" <> _}} 55 | 56 | # Average bandwith change causes update in master manifest 57 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 58 | 59 | refute_receive {SendStorage, _, _} 60 | 61 | :ok = Testing.Pipeline.terminate(pipeline) 62 | 63 | assert_received {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 64 | end 65 | 66 | test "video and audio track" do 67 | pipeline = mk_pipeline([{:audio, "audio_track"}, {:video, "video_track"}]) 68 | assert_receive {SendStorage, :store, %{type: :header, name: "audio_header" <> _}} 69 | assert_receive {SendStorage, :store, %{type: :header, name: "video_header" <> _}} 70 | 71 | send_buf(pipeline, "audio_track", 2) 72 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 73 | assert_receive {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 74 | assert_receive {SendStorage, :store, %{name: "audio_segment_0_" <> _}} 75 | assert_pipeline_notified(pipeline, :sink, {:track_playable, "audio_track"}) 76 | 77 | send_buf(pipeline, "video_track", 3) 78 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 79 | assert_receive {SendStorage, :store, %{type: :manifest, name: "video_" <> _}} 80 | assert_receive {SendStorage, :store, %{name: "video_segment_0_" <> _}} 81 | assert_pipeline_notified(pipeline, :sink, {:track_playable, "video_track"}) 82 | 83 | send_buf(pipeline, "audio_track", 4) 84 | assert_receive {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 85 | assert_receive {SendStorage, :store, %{name: "audio_segment_1_" <> _}} 86 | 87 | send_buf(pipeline, "video_track", 5) 88 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 89 | assert_receive {SendStorage, :store, %{type: :manifest, name: "video" <> _}} 90 | assert_receive {SendStorage, :store, %{name: "video_segment_1_" <> _}} 91 | assert_receive {SendStorage, :remove, %{name: "video_segment_0_" <> _}} 92 | 93 | send_buf(pipeline, "audio_track", 2) 94 | assert_receive {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 95 | assert_receive {SendStorage, :store, %{name: "audio_segment_2_" <> _}} 96 | assert_receive {SendStorage, :remove, %{name: "audio_segment_0_" <> _}} 97 | refute_receive {SendStorage, _, _} 98 | 99 | :ok = Testing.Pipeline.terminate(pipeline) 100 | 101 | assert_received {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 102 | assert_received {SendStorage, :store, %{type: :manifest, name: "video" <> _}} 103 | # Cache will be cleared on first track removal, thus index and that track manifest 104 | # will be stored again upon second track removal. 105 | assert_received {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 106 | assert_received {SendStorage, :store, %{type: :manifest, name: _}} 107 | refute_received {SendStorage, _, _} 108 | end 109 | 110 | test "audio and multiple video tracks" do 111 | pipeline = 112 | mk_pipeline([ 113 | {:video, "video_0"}, 114 | {:audio, "audio"}, 115 | {:video, "video_1"}, 116 | {:video, "video_2"} 117 | ]) 118 | 119 | assert_receive {SendStorage, :store, %{type: :header, name: "audio_header" <> _}} 120 | assert_receive {SendStorage, :store, %{type: :header, name: "video_header_" <> _}} 121 | assert_receive {SendStorage, :store, %{type: :header, name: "video_header_" <> _}} 122 | assert_receive {SendStorage, :store, %{type: :header, name: "video_header_" <> _}} 123 | refute_receive {SendStorage, _, _} 124 | 125 | send_buf(pipeline, "audio", 2) 126 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 127 | assert_receive {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 128 | assert_receive {SendStorage, :store, %{name: "audio_segment_0_" <> _}} 129 | refute_receive {SendStorage, _, _} 130 | assert_pipeline_notified(pipeline, :sink, {:track_playable, "audio"}) 131 | 132 | send_buf(pipeline, "video_1", 5) 133 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 134 | assert_receive {SendStorage, :store, %{type: :manifest, name: "video_1" <> _}} 135 | assert_receive {SendStorage, :store, %{name: "video_segment_0_video_1" <> _}} 136 | refute_receive {SendStorage, _, _} 137 | assert_pipeline_notified(pipeline, :sink, {:track_playable, "video_1"}) 138 | 139 | send_buf(pipeline, "audio", 4) 140 | assert_receive {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 141 | assert_receive {SendStorage, :store, %{name: "audio_segment_1_" <> _}} 142 | refute_receive {SendStorage, _, _} 143 | 144 | send_buf(pipeline, "video_2", 5) 145 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 146 | assert_receive {SendStorage, :store, %{type: :manifest, name: "video_2" <> _}} 147 | assert_receive {SendStorage, :store, %{name: "video_segment_0_video_2" <> _}} 148 | refute_receive {SendStorage, _, _} 149 | assert_pipeline_notified(pipeline, :sink, {:track_playable, "video_2"}) 150 | 151 | send_buf(pipeline, "video_2", 6) 152 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 153 | assert_receive {SendStorage, :store, %{type: :manifest, name: "video_2" <> _}} 154 | assert_receive {SendStorage, :store, %{name: "video_segment_1_video_2" <> _}} 155 | assert_receive {SendStorage, :remove, %{name: "video_segment_0_video_2" <> _}} 156 | refute_receive {SendStorage, _, _} 157 | 158 | send_buf(pipeline, "video_0", 2) 159 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 160 | assert_receive {SendStorage, :store, %{type: :manifest, name: "video_0" <> _}} 161 | assert_receive {SendStorage, :store, %{name: "video_segment_0_video_0" <> _}} 162 | refute_receive {SendStorage, _, _} 163 | assert_pipeline_notified(pipeline, :sink, {:track_playable, "video_0"}) 164 | 165 | send_buf(pipeline, "video_1", 6) 166 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 167 | assert_receive {SendStorage, :store, %{type: :manifest, name: "video_1" <> _}} 168 | assert_receive {SendStorage, :store, %{name: "video_segment_1_video_1" <> _}} 169 | assert_receive {SendStorage, :remove, %{name: "video_segment_0_video_1" <> _}} 170 | refute_receive {SendStorage, _, _} 171 | 172 | :ok = Testing.Pipeline.terminate(pipeline) 173 | end 174 | 175 | test "cleanup" do 176 | cleanup_after = Membrane.Time.seconds(1) 177 | pipeline = mk_pipeline([{:audio, "audio_track"}], cleanup_after: cleanup_after) 178 | assert_receive {SendStorage, :store, %{type: :header}} 179 | 180 | send_buf(pipeline, "audio_track", 2) 181 | assert_receive {SendStorage, :store, %{type: :manifest, name: "index.m3u8"}} 182 | assert_receive {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 183 | assert_receive {SendStorage, :store, %{name: "audio_segment_0_" <> _}} 184 | assert_pipeline_notified(pipeline, :sink, {:track_playable, "audio_track"}) 185 | 186 | :ok = Testing.Pipeline.terminate(pipeline) 187 | 188 | assert_receive {SendStorage, :store, %{type: :manifest, name: "audio" <> _}} 189 | 190 | Process.sleep(Membrane.Time.as_milliseconds(cleanup_after, :round)) 191 | 192 | assert_receive {SendStorage, :remove, %{type: :manifest, name: "index.m3u8"}} 193 | assert_receive {SendStorage, :remove, %{type: :manifest, name: "audio" <> _}} 194 | assert_receive {SendStorage, :remove, %{name: "audio_segment_0_" <> _}} 195 | assert_receive {SendStorage, :remove, %{type: :header}} 196 | refute_receive {SendStorage, _, _} 197 | end 198 | 199 | defp mk_pipeline(sources, opts \\ []) do 200 | import Membrane.ChildrenSpec 201 | 202 | sources = 203 | Enum.map(sources, fn {content_type, source_id} -> 204 | {{:source, source_id}, %Source{content_type: content_type, source_id: source_id}} 205 | end) 206 | 207 | segment_duration = Time.seconds(5) 208 | 209 | manifest_config = %Sink.ManifestConfig{module: Membrane.HTTPAdaptiveStream.HLS} 210 | track_config = %Sink.TrackConfig{target_window_duration: Time.seconds(5), mode: :vod} 211 | 212 | structure = 213 | [ 214 | child(:sink, %Sink{ 215 | manifest_config: manifest_config, 216 | track_config: track_config, 217 | storage: %SendStorage{destination: self()}, 218 | cleanup_after: opts[:cleanup_after] 219 | }) 220 | ] ++ 221 | Enum.map(sources, fn {{:source, source_id}, config} -> 222 | child({:source, source_id}, config) 223 | |> via_in(Pad.ref(:input, source_id), 224 | options: [track_name: source_id, segment_duration: segment_duration] 225 | ) 226 | |> get_child(:sink) 227 | end) 228 | 229 | Testing.Pipeline.start_link_supervised!(spec: structure) 230 | end 231 | 232 | defp send_buf(pipeline, source_id, duration) do 233 | buffer = %Buffer{ 234 | payload: "test_payload", 235 | metadata: %{duration: Time.seconds(duration), independent?: true, last_chunk?: true} 236 | } 237 | 238 | Testing.Pipeline.notify_child(pipeline, {:source, source_id}, {:buffer, buffer}) 239 | end 240 | end 241 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start(capture_log: true) 2 | --------------------------------------------------------------------------------