我正在Elm中做一个小应用程序。它在屏幕上显示一个计时器,当计时器达到零时,它会播放声音。我无法弄清楚如何从定时器发送消息(?)到声音播放器。发送来自elm子组件的信号
架构上,我有三个模块:Clock
模块表示定时器,PlayAudio
模块,可以播放音频和Main
模块联系在一起的Clock
模块和PlayAudio
模块。
理想的情况下,当时钟到达零,我想要做的事就像从Clock
模块发送信号。当时钟到达零,Clock
将信号发送到Main
,将其转发给PlayAudio
。
但是,从阅读榆树文档,好像比Main
处理的信号是不鼓励的任何其他。所以这导致我的第一个问题。建立这种状态变化的好方法是什么? Clock
的update
函数是否应返回是否已结束? (这是我如何做以下,但我会很开放,如何做的更好的建议。)
我的第二个问题是关于如何让播放声音。我将使用原始Javascript播放声音,我相信这意味着我必须使用端口。但是,我不确定如何与我的子模块PlayAudio
中的Main
中定义的端口进行交互。
以下是我正在使用的代码。
Clock.elm
:
module Clock (Model, init, Action, signal, update, view) where
import Html (..)
import Html.Attributes (..)
import Html.Events (..)
import LocalChannel (..)
import Signal
import Time (..)
-- MODEL
type ClockState = Running | Ended
type alias Model =
{ time: Time
, state: ClockState
}
init : Time -> Model
init initialTime =
{ time = initialTime
, state = Running
}
-- UPDATE
type Action = Tick Time
update : Action -> Model -> (Model, Bool)
update action model =
case action of
Tick tickTime ->
let hasEnded = model.time <= 1
newModel = { model | time <-
if hasEnded then 0 else model.time - tickTime
, state <-
if hasEnded then Ended else Running }
in (newModel, hasEnded)
-- VIEW
view : Model -> Html
view model =
div []
[ (toString model.time ++ toString model.state) |> text ]
signal : Signal Action
signal = Signal.map (always (1 * second) >> Tick) (every second)
PlaySound.elm
:
module PlaySound (Model, init, update, view) where
import Html (..)
import Html.Attributes (..)
import Html.Events (..)
import LocalChannel (..)
import Signal
import Time (..)
-- MODEL
type alias Model =
{ playing: Bool
}
init : Model
init =
{ playing = False
}
-- UPDATE
update : Bool -> Model -> Model
update shouldPlay model =
{ model | playing <- shouldPlay }
-- VIEW
view : Model -> Html
view model =
let node = if model.playing
then audio [ src "sounds/bell.wav"
, id "audiotag" ]
[]
else text "Not Playing"
in div [] [node]
Main.elm
:
module Main where
import Debug (..)
import Html (..)
import Html.Attributes (..)
import Html.Events (..)
import Html.Lazy (lazy, lazy2)
import Json.Decode as Json
import List
import LocalChannel as LC
import Maybe
import Signal
import String
import Time (..)
import Window
import Clock
import PlaySound
---- MODEL ----
-- The full application state of our todo app.
type alias Model =
{ clock : Clock.Model
, player : PlaySound.Model
}
emptyModel : Model
emptyModel =
{ clock = 10 * second |> Clock.init
, player = PlaySound.init
}
---- UPDATE ----
type Action
= NoOp
| ClockAction Clock.Action
-- How we update our Model on a given Action?
update : Action -> Model -> Model
update action model =
case action of
NoOp -> model
ClockAction clockAction ->
let (newClock, hasEnded) = Clock.update clockAction model.clock
newPlaySound = PlaySound.update hasEnded model.player
in { model | clock <- newClock
, player <- newPlaySound }
---- VIEW ----
view : Model -> Html
view model =
let context = Clock.Context (LC.create ClockAction actionChannel)
in div [ ]
[ Clock.view context model.clock
, PlaySound.view model.player
]
---- INPUTS ----
-- wire the entire application together
main : Signal Html
main = Signal.map view model
-- manage the model of our application over time
model : Signal Model
model = Signal.foldp update initialModel allSignals
allSignals : Signal Action
allSignals = Signal.mergeMany
[ Signal.map ClockAction Clock.signal
, Signal.subscribe actionChannel
]
initialModel : Model
initialModel = emptyModel
-- updates from user input
actionChannel : Signal.Channel Action
actionChannel = Signal.channel NoOp
port playSound : Signal()
port playSound = ???
index.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="js/elm.js" type="text/javascript"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<script type="text/javascript">
var todomvc = Elm.fullscreen(Elm.Main);
todomvc.ports.playSound.subscribe(function() {
setTimeout(function() {
document.getElementById('audiotag').play();
}, 50);
});
</script>
</body>
</html>