1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use std::cell::Cell;

use dom_struct::dom_struct;
use servo_media::audio::node::{
    AudioNodeInit, AudioNodeMessage, AudioScheduledSourceNodeMessage, OnEndedCallback,
};

use crate::dom::audionode::{AudioNode, UnwrappedAudioNodeOptions};
use crate::dom::baseaudiocontext::BaseAudioContext;
use crate::dom::bindings::codegen::Bindings::AudioScheduledSourceNodeBinding::AudioScheduledSourceNodeMethods;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::DomObject;
use crate::task_source::TaskSource;

#[dom_struct]
pub struct AudioScheduledSourceNode {
    node: AudioNode,
    has_start: Cell<bool>,
    has_stop: Cell<bool>,
}

impl AudioScheduledSourceNode {
    #[allow(crown::unrooted_must_root)]
    pub fn new_inherited(
        node_type: AudioNodeInit,
        context: &BaseAudioContext,
        options: UnwrappedAudioNodeOptions,
        number_of_inputs: u32,
        number_of_outputs: u32,
    ) -> Fallible<AudioScheduledSourceNode> {
        Ok(AudioScheduledSourceNode {
            node: AudioNode::new_inherited(
                node_type,
                context,
                options,
                number_of_inputs,
                number_of_outputs,
            )?,
            has_start: Cell::new(false),
            has_stop: Cell::new(false),
        })
    }

    pub fn node(&self) -> &AudioNode {
        &self.node
    }

    pub fn has_start(&self) -> bool {
        self.has_start.get()
    }
}

impl AudioScheduledSourceNodeMethods for AudioScheduledSourceNode {
    // https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-onended
    event_handler!(ended, GetOnended, SetOnended);

    // https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-start
    fn Start(&self, when: Finite<f64>) -> Fallible<()> {
        if *when < 0. {
            return Err(Error::Range("'when' must be a positive value".to_owned()));
        }

        if self.has_start.get() || self.has_stop.get() {
            return Err(Error::InvalidState);
        }

        let this = Trusted::new(self);
        let global = self.global();
        let window = global.as_window();
        let (task_source, canceller) = window
            .task_manager()
            .dom_manipulation_task_source_with_canceller();
        let callback = OnEndedCallback::new(move || {
            let _ = task_source.queue_with_canceller(
                task!(ended: move || {
                    let this = this.root();
                    let global = this.global();
                    let window = global.as_window();
                    window.task_manager().dom_manipulation_task_source().queue_simple_event(
                        this.upcast(),
                        atom!("ended"),
                        window
                        );
                }),
                &canceller,
            );
        });

        self.node()
            .message(AudioNodeMessage::AudioScheduledSourceNode(
                AudioScheduledSourceNodeMessage::RegisterOnEndedCallback(callback),
            ));

        self.has_start.set(true);
        self.node
            .message(AudioNodeMessage::AudioScheduledSourceNode(
                AudioScheduledSourceNodeMessage::Start(*when),
            ));
        Ok(())
    }

    // https://webaudio.github.io/web-audio-api/#dom-audioscheduledsourcenode-stop
    fn Stop(&self, when: Finite<f64>) -> Fallible<()> {
        if *when < 0. {
            return Err(Error::Range("'when' must be a positive value".to_owned()));
        }

        if !self.has_start.get() {
            return Err(Error::InvalidState);
        }
        self.has_stop.set(true);
        self.node
            .message(AudioNodeMessage::AudioScheduledSourceNode(
                AudioScheduledSourceNodeMessage::Stop(*when),
            ));
        Ok(())
    }
}