1 /**
2   Implementation of types that can be played on a VoiceClient
3 */
4 module dscord.voice.playable;
5 
6 import dcad.types : DCAFile;
7 
8 /**
9   An interface representing a type which can be played over a VoiceClient.
10 */
11 interface Playable {
12   /// Duration of the frame in milliseconds
13   const short getFrameDuration();
14 
15   /// Size of the frame in bytes
16   const short getFrameSize();
17 
18   /// Returns the next frame to be played
19   ubyte[] nextFrame();
20 
21   /// Returns true while there are more frames to be played
22   bool hasMoreFrames();
23 
24   /// Called when the Playable begins to be played
25   void start();
26 }
27 
28 /**
29   Playable implementation for DCAFiles
30 */
31 class DCAPlayable : Playable {
32   private {
33     DCAFile file;
34 
35     size_t frameIndex;
36   }
37 
38   this(DCAFile file) {
39     this.file = file;
40   }
41 
42   // TODO: Don't hardcode this
43   const short getFrameDuration() {
44     return 20;
45   }
46 
47   const short getFrameSize() {
48     return 960;
49   }
50 
51   bool hasMoreFrames() {
52     return this.frameIndex + 1 < this.file.frames.length;
53   }
54 
55   ubyte[] nextFrame() {
56     this.frameIndex++;
57     return this.file.frames[this.frameIndex - 1].data;
58   }
59 
60   void start() {}
61 }
62 
63 interface PlaylistProvider {
64   bool hasNext();
65   Playable getNext();
66 }
67 
68 class Playlist : Playable {
69   PlaylistProvider provider;
70   Playable current;
71 
72   this(PlaylistProvider provider) {
73     this.provider = provider;
74   }
75 
76   const short getFrameDuration() {
77     return this.current.getFrameDuration();
78   }
79 
80   const short getFrameSize() {
81     return this.current.getFrameSize();
82   }
83 
84   bool hasMoreFrames() {
85     if (!this.current) return false;
86     if (this.current.hasMoreFrames()) return true;
87     if (this.provider.hasNext()) return true;
88     return false;
89   }
90 
91   ubyte[] nextFrame() {
92     if (!this.current.hasMoreFrames()) {
93       if (this.provider.hasNext()) {
94         this.current = this.provider.getNext();
95       } else{
96         this.current = null;
97       }
98     }
99 
100     return this.current.nextFrame();
101   }
102 
103   void start() {
104     this.next();
105   }
106 
107   void next() {
108     if (this.provider.hasNext()) {
109       this.current = this.provider.getNext();
110     } else {
111       this.current = null;
112     }
113   }
114 }
115 
116 /**
117   Simple Playlist provider.
118 */
119 class SimplePlaylistProvider : PlaylistProvider {
120   private {
121     Playable[] playlist;
122   }
123 
124   this(Playable[] playlist) {
125     this.playlist = playlist;
126   }
127 
128   bool hasNext() {
129     return (this.playlist.length > 0);
130   }
131 
132   Playable getNext() {
133     assert(this.hasNext(), "No next Playable for SimplePlaylistProvider");
134     Playable next = this.playlist[0];
135     this.playlist = this.playlist[1..$];
136     return next;
137   }
138 
139   @property size_t length() {
140     return this.playlist.length;
141   }
142 
143   void add(Playable p) {
144     this.playlist ~= p;
145   }
146 
147   void empty() {
148     this.playlist = [];
149   }
150 }