Skip to content

Commit

Permalink
Add BYTERANGE support for media init
Browse files Browse the repository at this point in the history
  • Loading branch information
Qizot committed Mar 13, 2024
1 parent f475623 commit e3e3bd4
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 32 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The package can be installed by adding `ex_m3u8` into your list of dependencies
```elixir
def deps do
[
{:ex_m3u8, "~> 0.14.0"}
{:ex_m3u8, "~> 0.14.1"}
]
end
```
Expand Down
24 changes: 24 additions & 0 deletions lib/ex_m3u8/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,28 @@ defmodule ExM3U8.Helpers do

@spec quoted_string(String.t()) :: String.t()
def quoted_string(value), do: ~s("#{value}")

@spec parse_byte_range(String.t()) ::
{:ok, {size :: non_neg_integer(), offset :: non_neg_integer() | nil}}
| {:error, String.t()}
def parse_byte_range(value) do
error = {:error, "invalid byte range"}

case String.split(value, "@", parts: 2) do
[size, offset] ->
case {Integer.parse(size), Integer.parse(offset)} do
{{size, ""}, {offset, ""}} -> {:ok, {size, offset}}
_other -> error
end

[size] ->
case Integer.parse(size) do
{size, ""} -> {:ok, {size, nil}}
:error -> error
end

_other ->
error
end
end
end
47 changes: 41 additions & 6 deletions lib/ex_m3u8/tags/media_init.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,69 @@ defmodule ExM3U8.Tags.MediaInit do
"""
@behaviour ExM3U8.Deserializer.AttributesDeserializer

use ExM3U8.DSL, disable_loaders: [:int, :float, :boolean]
use TypedStruct

alias ExM3U8.Deserializer.AttributesDeserializer

typedstruct enforce: true do
field :uri, String.t()

field :byte_range, {size :: pos_integer(), offset :: non_neg_integer() | nil} | nil,
default: nil
end

load_attribute :uri,
attribute: "URI",
type: :string,
allow_empty?: false

defp load(:byte_range, attrs) do
case Map.get(attrs, "BYTERANGE", nil) do
nil -> {:ok, nil}
byte_range -> ExM3U8.Helpers.parse_byte_range(byte_range)
end
end

@impl true
def deserialize(attrs) do
case Map.fetch(attrs, "URI") do
{:ok, uri} -> {:ok, %__MODULE__{uri: uri}}
:error -> {:error, "invalid uri"}
end
AttributesDeserializer.deserialize_struct_fields(
__MODULE__,
&load/2,
attrs
)
end

defimpl ExM3U8.Serializer do
use ExM3U8.DSL

require ExM3U8.Helpers

alias ExM3U8.Helpers

@impl true
def serialize(%@for{uri: uri}) do
[Helpers.tag_prefix(), "MAP:URI=", Helpers.quoted_string(uri)]
def serialize(%@for{} = data) do
[Helpers.tag_prefix(), "MAP:", Helpers.merge_attributes(data, &dump/1, &sorter/1)]
end

defp sorter(field) do
Helpers.generate_sorter(field, [
:uri,
:byte_range
])
end

dump_attribute :uri,
attribute: "URI",
quoted_string?: true,
skip_empty?: false

defp dump({:byte_range, nil}), do: []

defp dump({:byte_range, {size, nil}}),
do: ["BYTERANGE=", ~s("#{size}")]

defp dump({:byte_range, {size, offset}}),
do: ["BYTERANGE=", ~s("#{size}@#{offset}")]
end
end
23 changes: 1 addition & 22 deletions lib/ex_m3u8/tags/part.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,7 @@ defmodule ExM3U8.Tags.Part do
defp load(:byte_range, attrs) do
case Map.get(attrs, "BYTERANGE", nil) do
nil -> {:ok, nil}
byte_range -> parse_byte_range(byte_range)
end
end

defp parse_byte_range(byte_range) do
error = {:error, "invalid byte range"}

case String.split(byte_range, "@", parts: 2) do
[size, offset] ->
case {Integer.parse(size), Integer.parse(offset)} do
{{size, ""}, {offset, ""}} -> {:ok, {size, offset}}
_other -> error
end

[size] ->
case Integer.parse(size) do
{size, ""} -> {:ok, {size, nil}}
:error -> error
end

_other ->
error
byte_range -> ExM3U8.Helpers.parse_byte_range(byte_range)
end
end

Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule ExM3U8.MixProject do
use Mix.Project

@version "0.14.0"
@version "0.14.1"
@github_url "https://github.com/membraneframework/ex_m3u8"

def project do
Expand Down
36 changes: 34 additions & 2 deletions test/ex_m3u8/tags/media_init_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,49 @@ defmodule ExM3U8.Tags.MediaInitTest do
#EXT-X-MAP:URI="header.mp4"
"""
|> String.trim_trailing() == serialize(header)

header = %ExM3U8.Tags.MediaInit{
uri: "header.mp4",
byte_range: {100, 0}
}

assert """
#EXT-X-MAP:URI="header.mp4",BYTERANGE="100@0"
"""
|> String.trim_trailing() == serialize(header)

header = %ExM3U8.Tags.MediaInit{
uri: "header.mp4",
byte_range: {100, nil}
}

assert """
#EXT-X-MAP:URI="header.mp4",BYTERANGE="100"
"""
|> String.trim_trailing() == serialize(header)
end

test "deserialize media init" do
attributes = ~s(URI="header.mp4",BYTERANGE="100@0")
{:ok, attrs} = ExM3U8.Deserializer.AttributesList.parse(attributes)

header = %ExM3U8.Tags.MediaInit{
uri: "header.mp4",
byte_range: {100, 0}
}

assert {:ok, ^header} = ExM3U8.Tags.MediaInit.deserialize(attrs)

attributes = ~s(URI="header.mp4")
{:ok, attrs} = ExM3U8.Deserializer.AttributesList.parse(attributes)

header = %ExM3U8.Tags.MediaInit{
uri: "header.mp4"
uri: "header.mp4",
byte_range: nil
}

assert {:ok, ^header} = ExM3U8.Tags.MediaInit.deserialize(attrs)
assert {:error, "invalid uri"} = ExM3U8.Tags.MediaInit.deserialize(%{})

assert {:error, "missing value"} = ExM3U8.Tags.MediaInit.deserialize(%{})
end
end

0 comments on commit e3e3bd4

Please sign in to comment.