-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Kino.Audio and Kino.Video (#311)
Co-authored-by: Jonatan Kłosko <[email protected]>
- Loading branch information
1 parent
70fc3ad
commit 4759cc5
Showing
7 changed files
with
277 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
export function init(ctx, [{ type, opts }, content]) { | ||
ctx.root.innerHTML = ` | ||
<div class="root"> | ||
<audio ${opts} src="${createDataUrl(content, type)}" style="height: 150px"/> | ||
</div> | ||
`; | ||
} | ||
|
||
function bufferToBase64(buffer) { | ||
let binaryString = ""; | ||
const bytes = new Uint8Array(buffer); | ||
const length = bytes.byteLength; | ||
|
||
for (let i = 0; i < length; i++) { | ||
binaryString += String.fromCharCode(bytes[i]); | ||
} | ||
|
||
return btoa(binaryString); | ||
}; | ||
|
||
function createDataUrl(content, type){ | ||
return `data:${type};base64,${bufferToBase64(content)}` | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
export function init(ctx, [{ type, opts }, content]) { | ||
ctx.root.innerHTML = ` | ||
<div class="root"> | ||
<video ${opts} src="${createDataUrl(content, type)}" style="max-height: 500px"/> | ||
</div> | ||
`; | ||
} | ||
|
||
function bufferToBase64(buffer) { | ||
let binaryString = ""; | ||
const bytes = new Uint8Array(buffer); | ||
const length = bytes.byteLength; | ||
|
||
for (let i = 0; i < length; i++) { | ||
binaryString += String.fromCharCode(bytes[i]); | ||
} | ||
|
||
return btoa(binaryString); | ||
}; | ||
|
||
function createDataUrl(content, type){ | ||
return `data:${type};base64,${bufferToBase64(content)}` | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
defmodule Kino.Audio do | ||
@moduledoc """ | ||
A kino for rendering a binary audio. | ||
## Examples | ||
content = File.read!("/path/to/audio.wav") | ||
Kino.Audio.new(content, :wav) | ||
content = File.read!("/path/to/audio.wav") | ||
Kino.Audio.new(content, :wav, autoplay: true, loop: true) | ||
""" | ||
|
||
use Kino.JS, assets_path: "lib/assets/audio" | ||
use Kino.JS.Live | ||
|
||
@type t :: Kino.JS.Live.t() | ||
|
||
@type mime_type :: binary() | ||
@type common_audio_type :: :wav | :mp3 | :mpeg | :ogg | ||
|
||
@doc """ | ||
Creates a new kino displaying the given binary audio. | ||
The given type be either `:wav`, `:mp3`/`:mpeg`, `:ogg` | ||
or a string with audio MIME type. | ||
## Options | ||
* `:autoplay` - whether the audio should start playing as soon as | ||
it is rendered. Defaults to `false` | ||
* `:loop` - whether the audio should loop. Defaults to `false` | ||
* `:muted` - whether the audio should be muted. Defaults to `false` | ||
""" | ||
@spec new(binary(), common_audio_type() | mime_type(), keyword()) :: t() | ||
def new(content, type, opts \\ []) when is_binary(content) do | ||
opts = | ||
Keyword.validate!(opts, | ||
autoplay: false, | ||
loop: false, | ||
muted: false | ||
) | ||
|
||
Kino.JS.Live.new(__MODULE__, %{ | ||
content: content, | ||
type: mime_type!(type), | ||
opts: | ||
Enum.reduce(opts, "controls", fn {opt, val}, acc -> | ||
if val do | ||
"#{acc} #{opt}" | ||
else | ||
acc | ||
end | ||
end) | ||
}) | ||
end | ||
|
||
@impl true | ||
def init(assigns, ctx) do | ||
{:ok, assign(ctx, assigns)} | ||
end | ||
|
||
@impl true | ||
def handle_connect(%{assigns: %{content: content, type: type, opts: opts}} = ctx) do | ||
payload = {:binary, %{type: type, opts: opts}, content} | ||
{:ok, payload, ctx} | ||
end | ||
|
||
defp mime_type!(:wav), do: "audio/wav" | ||
defp mime_type!(:mp3), do: "audio/mpeg" | ||
defp mime_type!(:mpeg), do: "audio/mpeg" | ||
defp mime_type!(:ogg), do: "audio/ogg" | ||
defp mime_type!("audio/" <> _ = mime_type), do: mime_type | ||
|
||
defp mime_type!(other) do | ||
raise ArgumentError, | ||
"expected audio type to be either :wav, :mp3, :mpeg, :ogg, or an audio MIME type string, got: #{inspect(other)}" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
defmodule Kino.Video do | ||
@moduledoc """ | ||
A kino for rendering a binary video. | ||
## Examples | ||
content = File.read!("/path/to/video.mp4") | ||
Kino.Video.new(content, :mp4) | ||
content = File.read!("/path/to/video.mp4") | ||
Kino.Video.new(content, :mp4, autoplay: true, loop: true) | ||
""" | ||
|
||
use Kino.JS, assets_path: "lib/assets/video" | ||
use Kino.JS.Live | ||
|
||
@type t :: Kino.JS.Live.t() | ||
|
||
@type mime_type :: binary() | ||
@type common_video_type :: :mp4 | :ogg | :avi | :mwv | :mov | ||
|
||
@doc """ | ||
Creates a new kino displaying the given binary video. | ||
The given type be either `:mp4`, `:ogg`, `:avi`, `:wmv`, `:mov` | ||
or a string with video MIME type. | ||
## Options | ||
* `:autoplay` - whether the video should start playing as soon as | ||
it is rendered. Defaults to `false` | ||
* `:loop` - whether the video should loop. Defaults to `false` | ||
* `:muted` - whether the video should be muted. Defaults to `false` | ||
""" | ||
@spec new(binary(), common_video_type() | mime_type(), list()) :: t() | ||
def new(content, type, opts \\ []) when is_binary(content) do | ||
opts = | ||
Keyword.validate!(opts, | ||
autoplay: false, | ||
loop: false, | ||
muted: false | ||
) | ||
|
||
Kino.JS.Live.new(__MODULE__, %{ | ||
content: content, | ||
type: mime_type!(type), | ||
opts: | ||
Enum.reduce(opts, "controls", fn {opt, val}, acc -> | ||
if val do | ||
"#{acc} #{opt}" | ||
else | ||
acc | ||
end | ||
end) | ||
}) | ||
end | ||
|
||
@impl true | ||
def init(assigns, ctx) do | ||
{:ok, assign(ctx, assigns)} | ||
end | ||
|
||
@impl true | ||
def handle_connect(%{assigns: %{content: content, type: type, opts: opts}} = ctx) do | ||
payload = {:binary, %{type: type, opts: opts}, content} | ||
{:ok, payload, ctx} | ||
end | ||
|
||
defp mime_type!(:mp4), do: "video/mp4" | ||
defp mime_type!(:ogg), do: "video/ogg" | ||
defp mime_type!(:avi), do: "video/x-msvideo" | ||
defp mime_type!(:wmv), do: "video/x-ms-wmv" | ||
defp mime_type!(:mov), do: "video/quicktime" | ||
defp mime_type!("video/" <> _ = mime_type), do: mime_type | ||
|
||
defp mime_type!(other) do | ||
raise ArgumentError, | ||
"expected video type to be either :mp4, :ogg, :avi, :wmv, :mov, or an video MIME type string, got: #{inspect(other)}" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
defmodule Kino.AudioTest do | ||
use Kino.LivebookCase, async: true | ||
|
||
describe "new/2" do | ||
test "raises an error for a non-image MIME type" do | ||
assert_raise ArgumentError, | ||
~s{expected audio type to be either :wav, :mp3, :mpeg, :ogg, or an audio MIME type string, got: "application/json"}, | ||
fn -> | ||
Kino.Audio.new(<<>>, "application/json") | ||
end | ||
end | ||
|
||
test "raises an error for an invalid type shorthand" do | ||
assert_raise ArgumentError, | ||
"expected audio type to be either :wav, :mp3, :mpeg, :ogg, or an audio MIME type string, got: :invalid", | ||
fn -> | ||
Kino.Audio.new(<<>>, :invalid) | ||
end | ||
end | ||
|
||
test "mime type shorthand and default opts" do | ||
kino = Kino.Audio.new(<<>>, :wav) | ||
assert {:binary, %{type: "audio/wav", opts: "controls"}, <<>>} == connect(kino) | ||
end | ||
|
||
test "custom mime type and custom opts" do | ||
kino = Kino.Audio.new(<<>>, "audio/mp2", loop: true) | ||
assert {:binary, %{type: "audio/mp2", opts: "controls loop"}, <<>>} == connect(kino) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
defmodule Kino.VideoTest do | ||
use Kino.LivebookCase, async: true | ||
|
||
describe "new/2" do | ||
test "raises an error for a non-image MIME type" do | ||
assert_raise ArgumentError, | ||
~s{expected video type to be either :mp4, :ogg, :avi, :wmv, :mov, or an video MIME type string, got: "application/json"}, | ||
fn -> | ||
Kino.Video.new(<<>>, "application/json") | ||
end | ||
end | ||
|
||
test "raises an error for an invalid type shorthand" do | ||
assert_raise ArgumentError, | ||
"expected video type to be either :mp4, :ogg, :avi, :wmv, :mov, or an video MIME type string, got: :invalid", | ||
fn -> | ||
Kino.Video.new(<<>>, :invalid) | ||
end | ||
end | ||
|
||
test "mime type shorthand and default opts" do | ||
kino = Kino.Video.new(<<>>, :mp4) | ||
assert {:binary, %{type: "video/mp4", opts: "controls"}, <<>>} == connect(kino) | ||
end | ||
|
||
test "custom mime type and custom opts" do | ||
kino = Kino.Video.new(<<>>, "video/h123", loop: true) | ||
assert {:binary, %{type: "video/h123", opts: "controls loop"}, <<>>} == connect(kino) | ||
end | ||
end | ||
end |