├── Free_Fonts.h
├── LICENSE
├── README.md
├── copy to server
├── README.md
└── eSP32.php
├── eStreamPlayer32.ino
├── icons.h
├── img
├── favorites.png
├── library.png
├── overlay.png
└── webradio.png
├── index.htm
├── index.htm.gz
├── index_htm_gz.h
├── percentEncode.h
├── playList.cpp
├── playList.h
├── presets.h
└── system_setup.h
/Free_Fonts.h:
--------------------------------------------------------------------------------
1 | // Attach this header file to your sketch to use the GFX Free Fonts. You can write
2 | // sketches without it, but it makes referencing them easier.
3 |
4 | // This calls up ALL the fonts but they only get loaded if you actually
5 | // use them in your sketch.
6 | //
7 | // No changes are needed to this header file unless new fonts are added to the
8 | // library "Fonts/GFXFF" folder.
9 | //
10 | // To save a lot of typing long names, each font can easily be referenced in the
11 | // sketch in three ways, either with:
12 | //
13 | // 1. Font file name with the & in front such as &FreeSansBoldOblique24pt7b
14 | // an example being:
15 | //
16 | // tft.setFreeFont(&FreeSansBoldOblique24pt7b);
17 | //
18 | // 2. FF# where # is a number determined by looking at the list below
19 | // an example being:
20 | //
21 | // tft.setFreeFont(FF32);
22 | //
23 | // 3. An abbreviation of the file name. Look at the list below to see
24 | // the abbreviations used, for example:
25 | //
26 | // tft.setFreeFont(FSSBO24)
27 | //
28 | // Where the letters mean:
29 | // F = Free font
30 | // M = Mono
31 | // SS = Sans Serif (double S to distinguish is form serif fonts)
32 | // S = Serif
33 | // B = Bold
34 | // O = Oblique (letter O not zero)
35 | // I = Italic
36 | // # = point size, either 9, 12, 18 or 24
37 | //
38 | // Setting the font to NULL will select the GLCD font:
39 | //
40 | // tft.setFreeFont(NULL); // Set font to GLCD
41 |
42 | #define LOAD_GFXFF
43 |
44 | #ifdef LOAD_GFXFF // Only include the fonts if LOAD_GFXFF is defined in User_Setup.h
45 |
46 | // Use these when printing or drawing text in GLCD and high rendering speed fonts
47 | #define GFXFF 1
48 | #define GLCD 0
49 | #define FONT2 2
50 | #define FONT4 4
51 | #define FONT6 6
52 | #define FONT7 7
53 | #define FONT8 8
54 |
55 | // Use the following when calling setFont()
56 | //
57 | // Reserved for GLCD font // FF0
58 | //
59 |
60 | #define TT1 &TomThumb
61 |
62 | #define FM9 &FreeMono9pt7b
63 | #define FM12 &FreeMono12pt7b
64 | #define FM18 &FreeMono18pt7b
65 | #define FM24 &FreeMono24pt7b
66 |
67 | #define FMB9 &FreeMonoBold9pt7b
68 | #define FMB12 &FreeMonoBold12pt7b
69 | #define FMB18 &FreeMonoBold18pt7b
70 | #define FMB24 &FreeMonoBold24pt7b
71 |
72 | #define FMO9 &FreeMonoOblique9pt7b
73 | #define FMO12 &FreeMonoOblique12pt7b
74 | #define FMO18 &FreeMonoOblique18pt7b
75 | #define FMO24 &FreeMonoOblique24pt7b
76 |
77 | #define FMBO9 &FreeMonoBoldOblique9pt7b
78 | #define FMBO12 &FreeMonoBoldOblique12pt7b
79 | #define FMBO18 &FreeMonoBoldOblique18pt7b
80 | #define FMBO24 &FreeMonoBoldOblique24pt7b
81 |
82 | #define FSS9 &FreeSans9pt7b
83 | #define FSS12 &FreeSans12pt7b
84 | #define FSS18 &FreeSans18pt7b
85 | #define FSS24 &FreeSans24pt7b
86 |
87 | #define FSSB9 &FreeSansBold9pt7b
88 | #define FSSB12 &FreeSansBold12pt7b
89 | #define FSSB18 &FreeSansBold18pt7b
90 | #define FSSB24 &FreeSansBold24pt7b
91 |
92 | #define FSSO9 &FreeSansOblique9pt7b
93 | #define FSSO12 &FreeSansOblique12pt7b
94 | #define FSSO18 &FreeSansOblique18pt7b
95 | #define FSSO24 &FreeSansOblique24pt7b
96 |
97 | #define FSSBO9 &FreeSansBoldOblique9pt7b
98 | #define FSSBO12 &FreeSansBoldOblique12pt7b
99 | #define FSSBO18 &FreeSansBoldOblique18pt7b
100 | #define FSSBO24 &FreeSansBoldOblique24pt7b
101 |
102 | #define FS9 &FreeSerif9pt7b
103 | #define FS12 &FreeSerif12pt7b
104 | #define FS18 &FreeSerif18pt7b
105 | #define FS24 &FreeSerif24pt7b
106 |
107 | #define FSI9 &FreeSerifItalic9pt7b
108 | #define FSI12 &FreeSerifItalic12pt7b
109 | #define FSI19 &FreeSerifItalic18pt7b
110 | #define FSI24 &FreeSerifItalic24pt7b
111 |
112 | #define FSB9 &FreeSerifBold9pt7b
113 | #define FSB12 &FreeSerifBold12pt7b
114 | #define FSB18 &FreeSerifBold18pt7b
115 | #define FSB24 &FreeSerifBold24pt7b
116 |
117 | #define FSBI9 &FreeSerifBoldItalic9pt7b
118 | #define FSBI12 &FreeSerifBoldItalic12pt7b
119 | #define FSBI18 &FreeSerifBoldItalic18pt7b
120 | #define FSBI24 &FreeSerifBoldItalic24pt7b
121 |
122 | #define FF0 NULL //ff0 reserved for GLCD
123 | #define FF1 &FreeMono9pt7b
124 | #define FF2 &FreeMono12pt7b
125 | #define FF3 &FreeMono18pt7b
126 | #define FF4 &FreeMono24pt7b
127 |
128 | #define FF5 &FreeMonoBold9pt7b
129 | #define FF6 &FreeMonoBold12pt7b
130 | #define FF7 &FreeMonoBold18pt7b
131 | #define FF8 &FreeMonoBold24pt7b
132 |
133 | #define FF9 &FreeMonoOblique9pt7b
134 | #define FF10 &FreeMonoOblique12pt7b
135 | #define FF11 &FreeMonoOblique18pt7b
136 | #define FF12 &FreeMonoOblique24pt7b
137 |
138 | #define FF13 &FreeMonoBoldOblique9pt7b
139 | #define FF14 &FreeMonoBoldOblique12pt7b
140 | #define FF15 &FreeMonoBoldOblique18pt7b
141 | #define FF16 &FreeMonoBoldOblique24pt7b
142 |
143 | #define FF17 &FreeSans9pt7b
144 | #define FF18 &FreeSans12pt7b
145 | #define FF19 &FreeSans18pt7b
146 | #define FF20 &FreeSans24pt7b
147 |
148 | #define FF21 &FreeSansBold9pt7b
149 | #define FF22 &FreeSansBold12pt7b
150 | #define FF23 &FreeSansBold18pt7b
151 | #define FF24 &FreeSansBold24pt7b
152 |
153 | #define FF25 &FreeSansOblique9pt7b
154 | #define FF26 &FreeSansOblique12pt7b
155 | #define FF27 &FreeSansOblique18pt7b
156 | #define FF28 &FreeSansOblique24pt7b
157 |
158 | #define FF29 &FreeSansBoldOblique9pt7b
159 | #define FF30 &FreeSansBoldOblique12pt7b
160 | #define FF31 &FreeSansBoldOblique18pt7b
161 | #define FF32 &FreeSansBoldOblique24pt7b
162 |
163 | #define FF33 &FreeSerif9pt7b
164 | #define FF34 &FreeSerif12pt7b
165 | #define FF35 &FreeSerif18pt7b
166 | #define FF36 &FreeSerif24pt7b
167 |
168 | #define FF37 &FreeSerifItalic9pt7b
169 | #define FF38 &FreeSerifItalic12pt7b
170 | #define FF39 &FreeSerifItalic18pt7b
171 | #define FF40 &FreeSerifItalic24pt7b
172 |
173 | #define FF41 &FreeSerifBold9pt7b
174 | #define FF42 &FreeSerifBold12pt7b
175 | #define FF43 &FreeSerifBold18pt7b
176 | #define FF44 &FreeSerifBold24pt7b
177 |
178 | #define FF45 &FreeSerifBoldItalic9pt7b
179 | #define FF46 &FreeSerifBoldItalic12pt7b
180 | #define FF47 &FreeSerifBoldItalic18pt7b
181 | #define FF48 &FreeSerifBoldItalic24pt7b
182 |
183 | // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
184 | // Now we define "s"tring versions for easy printing of the font name so:
185 | // tft.println(sFF5);
186 | // will print
187 | // Mono bold 9
188 | // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
189 |
190 | #define sFF0 "GLCD"
191 | #define sTT1 "Tom Thumb"
192 | #define sFF1 "Mono 9"
193 | #define sFF2 "Mono 12"
194 | #define sFF3 "Mono 18"
195 | #define sFF4 "Mono 24"
196 |
197 | #define sFF5 "Mono bold 9"
198 | #define sFF6 "Mono bold 12"
199 | #define sFF7 "Mono bold 18"
200 | #define sFF8 "Mono bold 24"
201 |
202 | #define sFF9 "Mono oblique 9"
203 | #define sFF10 "Mono oblique 12"
204 | #define sFF11 "Mono oblique 18"
205 | #define sFF12 "Mono oblique 24"
206 |
207 | #define sFF13 "Mono bold oblique 9"
208 | #define sFF14 "Mono bold oblique 12"
209 | #define sFF15 "Mono bold oblique 18"
210 | #define sFF16 "Mono bold oblique 24" // Full text line is too big for 480 pixel wide screen
211 |
212 | #define sFF17 "Sans 9"
213 | #define sFF18 "Sans 12"
214 | #define sFF19 "Sans 18"
215 | #define sFF20 "Sans 24"
216 |
217 | #define sFF21 "Sans bold 9"
218 | #define sFF22 "Sans bold 12"
219 | #define sFF23 "Sans bold 18"
220 | #define sFF24 "Sans bold 24"
221 |
222 | #define sFF25 "Sans oblique 9"
223 | #define sFF26 "Sans oblique 12"
224 | #define sFF27 "Sans oblique 18"
225 | #define sFF28 "Sans oblique 24"
226 |
227 | #define sFF29 "Sans bold oblique 9"
228 | #define sFF30 "Sans bold oblique 12"
229 | #define sFF31 "Sans bold oblique 18"
230 | #define sFF32 "Sans bold oblique 24"
231 |
232 | #define sFF33 "Serif 9"
233 | #define sFF34 "Serif 12"
234 | #define sFF35 "Serif 18"
235 | #define sFF36 "Serif 24"
236 |
237 | #define sFF37 "Serif italic 9"
238 | #define sFF38 "Serif italic 12"
239 | #define sFF39 "Serif italic 18"
240 | #define sFF40 "Serif italic 24"
241 |
242 | #define sFF41 "Serif bold 9"
243 | #define sFF42 "Serif bold 12"
244 | #define sFF43 "Serif bold 18"
245 | #define sFF44 "Serif bold 24"
246 |
247 | #define sFF45 "Serif bold italic 9"
248 | #define sFF46 "Serif bold italic 12"
249 | #define sFF47 "Serif bold italic 18"
250 | #define sFF48 "Serif bold italic 24"
251 |
252 | #else // LOAD_GFXFF not defined so setup defaults to prevent error messages
253 |
254 | // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
255 | // Free fonts are not loaded in User_Setup.h so we must define all as font 1
256 | // to prevent compile error messages
257 | // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
258 |
259 | #define GFXFF 1
260 | #define GLCD 1
261 | #define FONT2 2
262 | #define FONT4 4
263 | #define FONT6 6
264 | #define FONT7 7
265 | #define FONT8 8
266 |
267 | #define FF0 1
268 | #define FF1 1
269 | #define FF2 1
270 | #define FF3 1
271 | #define FF4 1
272 | #define FF5 1
273 | #define FF6 1
274 | #define FF7 1
275 | #define FF8 1
276 | #define FF9 1
277 | #define FF10 1
278 | #define FF11 1
279 | #define FF12 1
280 | #define FF13 1
281 | #define FF14 1
282 | #define FF15 1
283 | #define FF16 1
284 | #define FF17 1
285 | #define FF18 1
286 | #define FF19 1
287 | #define FF20 1
288 | #define FF21 1
289 | #define FF22 1
290 | #define FF23 1
291 | #define FF24 1
292 | #define FF25 1
293 | #define FF26 1
294 | #define FF27 1
295 | #define FF28 1
296 | #define FF29 1
297 | #define FF30 1
298 | #define FF31 1
299 | #define FF32 1
300 | #define FF33 1
301 | #define FF34 1
302 | #define FF35 1
303 | #define FF36 1
304 | #define FF37 1
305 | #define FF38 1
306 | #define FF39 1
307 | #define FF40 1
308 | #define FF41 1
309 | #define FF42 1
310 | #define FF43 1
311 | #define FF44 1
312 | #define FF45 1
313 | #define FF46 1
314 | #define FF47 1
315 | #define FF48 1
316 |
317 | #define FM9 1
318 | #define FM12 1
319 | #define FM18 1
320 | #define FM24 1
321 |
322 | #define FMB9 1
323 | #define FMB12 1
324 | #define FMB18 1
325 | #define FMB24 1
326 |
327 | #define FMO9 1
328 | #define FMO12 1
329 | #define FMO18 1
330 | #define FMO24 1
331 |
332 | #define FMBO9 1
333 | #define FMBO12 1
334 | #define FMBO18 1
335 | #define FMBO24 1
336 |
337 | #define FSS9 1
338 | #define FSS12 1
339 | #define FSS18 1
340 | #define FSS24 1
341 |
342 | #define FSSB9 1
343 | #define FSSB12 1
344 | #define FSSB18 1
345 | #define FSSB24 1
346 |
347 | #define FSSO9 1
348 | #define FSSO12 1
349 | #define FSSO18 1
350 | #define FSSO24 1
351 |
352 | #define FSSBO9 1
353 | #define FSSBO12 1
354 | #define FSSBO18 1
355 | #define FSSBO24 1
356 |
357 | #define FS9 1
358 | #define FS12 1
359 | #define FS18 1
360 | #define FS24 1
361 |
362 | #define FSI9 1
363 | #define FSI12 1
364 | #define FSI19 1
365 | #define FSI24 1
366 |
367 | #define FSB9 1
368 | #define FSB12 1
369 | #define FSB18 1
370 | #define FSB24 1
371 |
372 | #define FSBI9 1
373 | #define FSBI12 1
374 | #define FSBI18 1
375 | #define FSBI24 1
376 |
377 | #endif // LOAD_GFXFF
378 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Cellie
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # eStreamPlayer32
2 |
3 | An web-based esp32 program to play webradio and mp3/aac files from a lamp or llmp server.
4 |
5 | - Control eStreamPlayer with your phone, pc or tablet.
6 | - Play your local files.
7 | - Play preset radio stations.
8 | - Add new urls and save them to favorites.
9 |
10 | Sound output comes from an i2s dac.
Currently generic i2s dacs (with `BCK`, `WS` and `DOUT` pins), M5Stack Node and AI Thinker A1S AudioKit are supported.
11 |
12 | When flashed to an M5Stack with the Node base you can see some information on the display.
13 |
14 |
15 | 
16 |
17 | ### Limitations
18 |
19 | - The audioI2S library uses a lot of RAM. eStreamPlayer will only function properly on a board with PSRAM.
20 |
21 | - eStreamPlayer is written for playback over http. The esp32 does not speak NFS or SMB which are common ways to share files over a network.
22 |
Instead eStreamPlayer uses a php script on the server to navigate the music folders.
23 |
Copy this script to the server to use your music library.
24 |
**This is totally insecure and should only be used on a LAN!**
25 |
26 | But if you don't have a local music server you can still use eStreamPlayer to tune in to web radio stations and add your own radio stations to presets and favorites.
27 |
28 | ### Web-interface screenshots
29 |
30 | #### File info overlay
31 |
32 | 
33 |
34 | #### LIBRARY tab
35 |
36 | 
37 |
38 | #### WEBRADIO tab
39 |
40 | 
41 |
42 | #### FAVORITES tab
43 |
44 | 
45 |
46 | ### Setup
47 |
48 | Use [the latest ESP32 Arduino Core](https://github.com/espressif/arduino-esp32/releases/latest).
49 |
50 | 1. Download the [latest eStreamPlayer release](https://github.com/CelliesProjects/eStreamPlayer32/releases/latest).
51 | 2. Install the [required libraries](#software-needed) and the drivers for your dac. See [supported hardware](#supported-hardware).
52 | 3. Copy the php script to your music folder on the server.
53 | 4. Open the Arduino IDE.
54 | 5. Select Tools->Partition Scheme->No OTA(2MB APP/2MB FATFS).
55 |
If your board does not have that option you can select ESP32 Dev Module in Tools->Board.
56 | 6. Open `system_setup.h` and set your wifi credentials and set `SCRIPT_URL` to the php script location.
57 | 7. Flash the sketch to your esp32. Set `Tools->Core Debug Level->Info` before you flash so you can grab the ip address from the serial port.
58 | 8. On first boot or after a flash erase the fatfs has to be formatted. This will take a couple of seconds.
59 |
Flashing an update will not erase the fatfs data.
60 |
**Note:** Take care to select the same partition table when updating otherwise the partition will be formatted.
61 |
You can monitor the boot progress on the serial port.
62 | 9. Browse to the ip address shown on the serial port.
63 |
64 | ### Supported hardware
65 |
66 | Select a board or dac by uncommenting the relevant line in `system_setup.h`.
67 |
68 | - Generic i2s dac - Select `GENERIC_I2S_DAC` as board.
For dacs that only need `BCK`, `WS` and `DOUT` and no driver setup.
Default pins are `BCK` = 21, `WS` = 26, `DOUT` = 22.
69 | - M5 Stack Node - Select `M5STACK_NODE` to compile for M5Stack Node with `MCLK` on `GPIO0`.
You need the [wm8978-esp32](https://github.com/CelliesProjects/wm8978-esp32) library for this dac.
70 | - A1S Audio Kit - Select `A1S_AUDIO_KIT` to compile for ESP32-A1S Audio Kit.
You need the [AC101](https://github.com/Yveaux/AC101) library for this dac.
71 |
72 | ### Software needed
73 |
74 | - [ESP32-audioI2S](https://github.com/schreibfaul1/ESP32-audioI2S) (GNU General Public License v3.0)
75 | - [AsyncTCP](https://github.com/me-no-dev/AsyncTCP) (LGPL-3.0 License)
76 | - [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) (LGPL-3.0 License)
77 | - A [lamp](https://en.wikipedia.org/wiki/LAMP_%28software_bundle%29) or llmp webstack to serve local files.
78 |
Apache2 and lighttpd were tested and should work. The php script should be fairly version agnostic.
79 |
Note: Mp3 and aac files should have the `Content-Type: audio/mpeg` http headers set or the decoder will not recognise the files.
80 |
81 | ### Libraries used in the web interface
82 |
83 | - The used icons are from [material.io](https://material.io/tools/icons/?style=baseline) and are [available under Apache2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html).
84 | - [Reconnecting WebSocket](https://github.com/joewalnes/reconnecting-websocket) which is [available under MIT licence](https://github.com/joewalnes/reconnecting-websocket/blob/master/LICENSE.txt).
85 | - [Google Roboto font](https://fonts.google.com/specimen/Roboto) which is [available under Apache2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html).
86 | - [jQuery 3.4.1](https://code.jquery.com/jquery-3.4.1.js) which is [available under MIT license](https://jquery.org/license/).
87 |
88 | ````
89 | MIT License
90 |
91 | Copyright (c) 2020 Cellie
92 |
93 | Permission is hereby granted, free of charge, to any person obtaining a copy
94 | of this software and associated documentation files (the "Software"), to deal
95 | in the Software without restriction, including without limitation the rights
96 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
97 | copies of the Software, and to permit persons to whom the Software is
98 | furnished to do so, subject to the following conditions:
99 |
100 | The above copyright notice and this permission notice shall be included in all
101 | copies or substantial portions of the Software.
102 |
103 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
104 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
105 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
106 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
107 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
108 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
109 | SOFTWARE.
110 | ````
111 |
--------------------------------------------------------------------------------
/copy to server/README.md:
--------------------------------------------------------------------------------
1 | ### Use notes
2 |
3 | - Copy this file to the root of your music collection.
4 | - Make sure your fileserver serves mp3 files with the correct MIME type in the `Content-Type` header which should be `Content-Type: audio/mpeg`
5 |
--------------------------------------------------------------------------------
/copy to server/eSP32.php:
--------------------------------------------------------------------------------
1 | ');
3 | define(EMPTY_ICON, '
');
4 | define(UPFOLDER_ICON, '');
5 | define(AUDIO_ICON, '');
6 | define(START_ICON, '
');
7 |
8 | header('Access-Control-Allow-Origin: *');
9 |
10 | if(isset($_GET["folder"])){
11 | $path=rawurldecode($_GET["folder"]);
12 | if(strpos($path,"..")!==false)die("No traversing");//no folder traversing
13 | //don't serve an absolute path but make it relative by removing all leading '/' chars
14 | $cnt=0;
15 | while($path[$cnt]==='/')$cnt++;
16 | $path=substr($path,$cnt);
17 |
18 | if($path!==''){
19 | $path=$path.'/';
20 | if(!file_exists($path)){
21 | header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found",true,404);
22 | die("Requested resource could not be found.");
23 | }
24 | echo '
'.UPFOLDER_ICON.'
';
25 | }
26 |
27 | $validFiles="*.{[Mm][Pp]3,[Aa][Aa][Cc],[Ww][Aa][Vv],[Mm]3[Uu],[Pp][Ll][Ss],[Aa][Ss][Xx]}";
28 |
29 | foreach(glob($path."*",GLOB_ONLYDIR)as$filename){
30 | echo '';
31 | $pieces=explode('/',$filename);
32 | if(glob($filename.'/'.$validFiles,GLOB_BRACE)) {
33 | echo ADDFOLDER_ICON.START_ICON;
34 | }
35 | else {
36 | echo EMPTY_ICON.EMPTY_ICON;
37 | }
38 | echo ''.$pieces[count($pieces)-1].'
';
39 | }
40 | foreach(glob($path.$validFiles,GLOB_BRACE)as$filename){
41 | $pieces=explode('/',$filename);
42 | echo ''.AUDIO_ICON.START_ICON.''.$pieces[count($pieces)-1].'
';
43 | }
44 | die();
45 | }
46 | ?>
47 | eStreamPlayer32
48 |
49 |
--------------------------------------------------------------------------------
/eStreamPlayer32.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include /* https://github.com/me-no-dev/AsyncTCP */
3 | #include /* https://github.com/me-no-dev/ESPAsyncWebServer */
4 | #include /* https://github.com/schreibfaul1/ESP32-audioI2S */
5 |
6 | #include "percentEncode.h"
7 | #include "system_setup.h"
8 | #include "playList.h"
9 | #include "index_htm_gz.h"
10 | #include "icons.h"
11 |
12 | #define I2S_MAX_VOLUME 21
13 | #define I2S_INITIAL_VOLUME 5
14 |
15 | const char* VERSION_STRING = "eStreamPlayer32 v1.0.5";
16 |
17 | enum {
18 | PAUSED,
19 | PLAYING,
20 | PLAYLISTEND,
21 | } playerStatus{ PLAYLISTEND };
22 |
23 | #define NOTHING_PLAYING_VAL -1
24 | const char* NOTHING_PLAYING_STR{
25 | "Nothing playing"
26 | };
27 |
28 | /* websocket message headers */
29 | const char* VOLUME_HEADER{
30 | "volume\n"
31 | };
32 |
33 | const char* CURRENT_HEADER{ "currentPLitem\n" };
34 | const char* MESSAGE_HEADER{ "message\n" };
35 |
36 | int currentItem{ NOTHING_PLAYING_VAL };
37 |
38 | playList_t playList;
39 | AsyncWebServer server(80);
40 | AsyncWebSocket ws("/ws");
41 |
42 | #if defined(A1S_AUDIO_KIT)
43 | #include /* https://github.com/Yveaux/AC101 */
44 | /* A1S Audiokit I2S pins */
45 | #define I2S_BCK 27
46 | #define I2S_WS 26
47 | #define I2S_DOUT 25
48 | #define I2S_MCLK 0
49 | /* A1S Audiokit I2C pins */
50 | #define I2C_SCL 32
51 | #define I2C_SDA 33
52 | AC101 dac;
53 | #endif //A1S_AUDIO_KIT
54 |
55 | #if defined(M5STACK_NODE)
56 | #include
57 | #include /* https://github.com/CelliesProjects/wm8978-esp32 */
58 | #include "Free_Fonts.h"
59 | /* M5Stack Node I2S pins */
60 | #define I2S_BCK 5
61 | #define I2S_WS 13
62 | #define I2S_DOUT 2
63 | #define I2S_MCLK 0
64 | /* M5Stack Node WM8978 I2C pins */
65 | #define I2C_SDA 21
66 | #define I2C_SCL 22
67 |
68 | WM8978 dac;
69 |
70 | void M5_displayItemName(const playListItem& item) {
71 | const int LOC_X{ M5.Lcd.width() / 2 }, LOC_Y{ M5.Lcd.height() / 2 };
72 | M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
73 | M5.Lcd.setFreeFont(FSS12);
74 | M5.Lcd.fillRect(0, LOC_Y, 320, M5.Lcd.fontHeight(GFXFF), TFT_BLACK); //clear area
75 | if (!item.name && item.type == HTTP_FAVORITE) return; /* shortcut to just delete itemName on the lcd is to call 'M5_displayItemName({HTTP_FAVORITE})' */
76 | M5.Lcd.setTextDatum(TC_DATUM); // TC = Top Center
77 | switch (item.type) {
78 | case HTTP_FAVORITE:
79 | M5.Lcd.drawString(item.name, LOC_X, LOC_Y);
80 | break;
81 | case HTTP_FILE:
82 | M5.Lcd.drawString(item.url.substring(item.url.lastIndexOf("/") + 1), LOC_X, LOC_Y);
83 | break;
84 | case HTTP_PRESET:
85 | M5.Lcd.drawString(preset[item.index].name, LOC_X, LOC_Y);
86 | break;
87 | case HTTP_STREAM:
88 | M5.Lcd.drawString(item.url, LOC_X, LOC_Y);
89 | break;
90 | default: log_e("Unhandled item.type");
91 | }
92 | M5.Lcd.display();
93 | }
94 |
95 | void M5_displayCurrentAndTotal() {
96 | const int LOC_X{ M5.Lcd.width() / 2 }, LOC_Y{ 70 };
97 | M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
98 | M5.Lcd.setFreeFont(FSS18);
99 | M5.Lcd.fillRect(0, LOC_Y, 320, M5.Lcd.fontHeight(GFXFF), TFT_BLACK); //clear area
100 | M5.Lcd.setTextDatum(TC_DATUM); // TC = Top Center
101 | String currentAndTotal;
102 | currentAndTotal.concat(currentItem + 1); /* we are talking to humans here */
103 | currentAndTotal.concat(" / ");
104 | currentAndTotal.concat(playList.size());
105 | M5.Lcd.drawString(currentAndTotal, LOC_X, LOC_Y);
106 | M5.Lcd.display();
107 | }
108 | #endif //M5STACK_NODE
109 |
110 | Audio audio;
111 |
112 | struct {
113 | bool waiting{ false };
114 | String url;
115 | uint32_t clientId;
116 | } newUrl;
117 |
118 | inline __attribute__((always_inline)) void updateHighlightedItemOnClients() {
119 | ws.textAll(CURRENT_HEADER + String(currentItem));
120 | }
121 |
122 | inline __attribute__((always_inline)) void muteVolumeAndStopSong() {
123 | const auto saved = audio.getVolume();
124 | audio.setVolume(0);
125 | audio.stopSong();
126 | audio.loop();
127 | audio.setVolume(saved);
128 | }
129 |
130 | const String urlEncode(const String& s) {
131 | //https://en.wikipedia.org/wiki/Percent-encoding
132 | String encodedstr{ "" };
133 | for (int i = 0; i < s.length(); i++) {
134 | switch (s.charAt(i)) {
135 | case ' ':
136 | encodedstr.concat("%20");
137 | break;
138 | case '!':
139 | encodedstr.concat("%21");
140 | break;
141 | case '&':
142 | encodedstr.concat("%26");
143 | break;
144 | case 39:
145 | encodedstr.concat("%27"); //39 == single quote '
146 | break;
147 | default: encodedstr.concat(s.charAt(i));
148 | }
149 | }
150 | log_d("encoded url: %s", encodedstr.c_str());
151 | return encodedstr;
152 | }
153 |
154 | void playListHasEnded() {
155 | currentItem = NOTHING_PLAYING_VAL;
156 | playerStatus = PLAYLISTEND;
157 | audio_showstation(NOTHING_PLAYING_STR);
158 | audio_showstreamtitle(VERSION_STRING);
159 | updateHighlightedItemOnClients();
160 | log_d("End of playlist.");
161 |
162 | #if defined(M5STACK_NODE)
163 | M5_displayItemName({ HTTP_FAVORITE, "", "", 0 });
164 | M5_displayCurrentAndTotal();
165 | #endif //M5STACK_NODE
166 | }
167 |
168 | void updateFavoritesOnClients() {
169 | String s;
170 | ws.textAll(favoritesToString(s));
171 | log_d("Favorites and clients are updated.");
172 | }
173 |
174 | static char showstation[200]; // These are kept global to update new clients in loop()
175 | void audio_showstation(const char* info) {
176 | if (!strcmp(info, "")) return;
177 | playListItem item;
178 | playList.get(currentItem, item);
179 | snprintf(showstation, sizeof(showstation), "showstation\n%s\n%s", info, typeStr[item.type]);
180 | log_d("showstation: %s", showstation);
181 | ws.textAll(showstation);
182 | }
183 |
184 | static char streamtitle[200]; // These are kept global to update new clients in loop()
185 | void audio_showstreamtitle(const char* info) {
186 | snprintf(streamtitle, sizeof(streamtitle), "streamtitle\n%s", percentEncode(info).c_str());
187 | log_d("streamtitle: %s", streamtitle);
188 | ws.printfAll(streamtitle);
189 | }
190 |
191 | void audio_id3data(const char* info) {
192 | log_i("id3data: %s", info);
193 | ws.printfAll("id3data\n%s", info);
194 | }
195 |
196 | // https://sookocheff.com/post/networking/how-do-websockets-work/
197 | // https://noio-ws.readthedocs.io/en/latest/overview_of_websockets.html
198 |
199 | void onEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
200 | if (type == WS_EVT_CONNECT) {
201 | client->text(VOLUME_HEADER + String(audio.getVolume()));
202 | {
203 | String s;
204 | client->text(playList.toString(s));
205 | client->text(favoritesToString(s));
206 | }
207 | client->text(CURRENT_HEADER + String(currentItem));
208 | client->text(showstation);
209 | if (currentItem != NOTHING_PLAYING_VAL)
210 | client->text(streamtitle);
211 | else {
212 | char buffer[200];
213 | snprintf(buffer, sizeof(buffer), "streamtitle\n%s", VERSION_STRING);
214 | client->text(buffer);
215 | }
216 | log_d("ws[%s][%u] connect", server->url(), client->id());
217 | return;
218 | } else if (type == WS_EVT_DISCONNECT) {
219 | log_d("ws[%s][%u] disconnect: %u", server->url(), client->id());
220 | return;
221 | } else if (type == WS_EVT_ERROR) {
222 | log_e("ws[%s][%u] error(%u): %s", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
223 | return;
224 | } else if (type == WS_EVT_DATA) {
225 | AwsFrameInfo* info = (AwsFrameInfo*)arg;
226 |
227 | // here all data is contained in a single packet
228 | if (info->final && info->index == 0 && info->len == len) {
229 | if (info->opcode == WS_TEXT) {
230 | data[len] = 0;
231 |
232 | log_d("ws request: %s", reinterpret_cast(data));
233 |
234 | char* pch = strtok(reinterpret_cast(data), "\n");
235 | if (!pch) return;
236 |
237 | if (!strcmp("volume", pch)) {
238 | pch = strtok(NULL, "\n");
239 | if (pch) {
240 | const uint8_t volume = atoi(pch);
241 | audio.setVolume(volume > I2S_MAX_VOLUME ? I2S_MAX_VOLUME : volume);
242 | ws.textAll(VOLUME_HEADER + String(audio.getVolume()));
243 | }
244 | return;
245 | }
246 |
247 | else if (!strcmp("filetoplaylist", pch) || !strcmp("_filetoplaylist", pch)) {
248 | const bool startnow = (pch[0] == '_');
249 | const uint32_t previousSize = playList.size();
250 | pch = strtok(NULL, "\n");
251 | while (pch) {
252 | log_d("argument: %s", pch);
253 | playList.add({ HTTP_FILE, "", pch, 0 });
254 | pch = strtok(NULL, "\n");
255 | }
256 | const uint32_t itemsAdded{ playList.size() - previousSize };
257 | client->printf("%sAdded %i items to playlist", MESSAGE_HEADER, itemsAdded);
258 |
259 | log_d("Added %i items to playlist", addedSongs);
260 |
261 | if (!itemsAdded) return;
262 |
263 | if (startnow) {
264 | if (audio.isRunning()) muteVolumeAndStopSong();
265 | currentItem = previousSize - 1;
266 | playerStatus = PLAYING;
267 | return;
268 | }
269 | // start playing at the correct position if not already playing
270 | if (!audio.isRunning() && PAUSED != playerStatus) {
271 | currentItem = previousSize - 1;
272 | playerStatus = PLAYING;
273 | }
274 | return;
275 | }
276 |
277 | else if (!strcmp("clearlist", pch)) {
278 | if (!playList.size()) return;
279 | muteVolumeAndStopSong();
280 | playList.clear();
281 | playListHasEnded();
282 | return;
283 | }
284 |
285 | else if (!strcmp("playitem", pch)) {
286 | pch = strtok(NULL, "\n");
287 | if (pch) {
288 | const uint32_t index = atoi(pch);
289 | if (index < playList.size()) {
290 | muteVolumeAndStopSong();
291 | currentItem = index - 1;
292 | playerStatus = PLAYING;
293 | }
294 | }
295 | return;
296 | }
297 |
298 | else if (!strcmp("deleteitem", pch)) {
299 | if (!playList.size()) return;
300 | pch = strtok(NULL, "\n");
301 | if (!pch) return;
302 | const uint32_t index = atoi(pch);
303 | if (index == currentItem) {
304 | muteVolumeAndStopSong();
305 | playList.remove(index);
306 | if (!playList.size()) {
307 | playListHasEnded();
308 | return;
309 | }
310 | currentItem--;
311 | return;
312 | }
313 | if (index < playList.size()) {
314 | playList.remove(index);
315 | if (!playList.size()) {
316 | playListHasEnded();
317 | return;
318 | }
319 | } else return;
320 |
321 | if (currentItem != NOTHING_PLAYING_VAL && index < currentItem)
322 | currentItem--;
323 | return;
324 | }
325 | /*
326 | else if (!strcmp("pause", pch)) {
327 | switch (playerStatus) {
328 | case PAUSED :{
329 | const uint8_t savedVolume = audio.getVolume();
330 | audio.setVolume(0);
331 | audio.pauseResume();
332 | audio.loop();
333 | audio.setVolume(savedVolume);
334 | playerStatus = PLAYING;
335 | //send play icon to clients
336 | }
337 | break;
338 | case PLAYING : {
339 | const uint8_t savedVolume = audio.getVolume();
340 | audio.setVolume(0);
341 | audio.pauseResume();
342 | delay(2);
343 | audio.setVolume(savedVolume);
344 | playerStatus = PAUSED;
345 | //send pause icon to clients
346 | }
347 | break;
348 | default : {};
349 | }
350 | }
351 | */
352 | else if (!strcmp("previous", pch)) {
353 | if (PLAYLISTEND == playerStatus) return;
354 | log_d("current: %i size: %i", currentItem, playList.size());
355 | if (currentItem > 0) {
356 | muteVolumeAndStopSong();
357 | currentItem--;
358 | currentItem--;
359 | return;
360 | } else return;
361 | }
362 |
363 | else if (!strcmp("next", pch)) {
364 | if (PLAYLISTEND == playerStatus) return;
365 | log_d("current: %i size: %i", currentItem, playList.size());
366 | if (currentItem < playList.size() - 1) {
367 | muteVolumeAndStopSong();
368 | return;
369 | } else return;
370 | }
371 |
372 | else if (!strcmp("newurl", pch)) {
373 | pch = strtok(NULL, "\n");
374 | if (pch) {
375 | log_d("received new url: %s", pch);
376 | newUrl.url = pch;
377 | newUrl.clientId = client->id();
378 | newUrl.waiting = true;
379 | }
380 | return;
381 | }
382 |
383 | else if (!strcmp("currenttofavorites", pch)) {
384 | pch = strtok(NULL, "\n");
385 | if (pch)
386 | handleCurrentToFavorites((String)pch, client->id());
387 | return;
388 | }
389 |
390 | else if (!strcmp("favoritetoplaylist", pch) || !strcmp("_favoritetoplaylist", pch)) {
391 | const bool startNow = (pch[0] == '_');
392 | pch = strtok(NULL, "\n");
393 | if (pch)
394 | handleFavoriteToPlaylist((String)pch, startNow);
395 | return;
396 | }
397 |
398 | else if (!strcmp("deletefavorite", pch)) {
399 | pch = strtok(NULL, "\n");
400 | if (pch) {
401 | if (!FFat.remove("/" + (String)pch)) {
402 | ws.printf(client->id(), "%sCould not delete %s", MESSAGE_HEADER, pch);
403 | } else {
404 | ws.printfAll("%sDeleted favorite %s", MESSAGE_HEADER, pch);
405 | updateFavoritesOnClients();
406 | }
407 | }
408 | return;
409 | }
410 |
411 | else if (!strcmp("presetstation", pch) || !strcmp("_presetstation", pch)) {
412 | const bool startnow = (pch[0] == '_');
413 | const uint32_t index = atoi(strtok(NULL, "\n"));
414 | if (index < sizeof(preset) / sizeof(source)) { // only add really existing presets to the playlist
415 | playList.add({ HTTP_PRESET, "", "", index });
416 |
417 | if (!playList.isUpdated) return;
418 |
419 | log_d("Added '%s' to playlist", preset[index].name.c_str());
420 | client->printf("%sAdded '%s' to playlist", MESSAGE_HEADER, preset[index].name.c_str());
421 |
422 | if (startnow) {
423 | if (audio.isRunning()) muteVolumeAndStopSong();
424 | currentItem = playList.size() - 2;
425 | playerStatus = PLAYING;
426 | return;
427 | }
428 |
429 | // start playing at the correct position if not already playing
430 | if (!audio.isRunning() && PAUSED != playerStatus) {
431 | currentItem = playList.size() - 2;
432 | playerStatus = PLAYING;
433 | }
434 | return;
435 | }
436 | }
437 | }
438 | } else {
439 | //message is comprised of multiple frames or the frame is split into multiple packets
440 | static char* buffer = nullptr;
441 | if (info->index == 0) {
442 | if (info->num == 0) {
443 | log_d("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT) ? "text" : "binary");
444 | }
445 |
446 | log_d("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len);
447 | //allocate info->len bytes of memory
448 |
449 | if (!buffer) {
450 | // we need at least twice the amount of free memory that is requested (buffer + playlist data)
451 | if (info->len * 2 > ESP.getFreeHeap()) {
452 | client->printf("%sout of memory", MESSAGE_HEADER);
453 | client->close();
454 | return;
455 | }
456 | buffer = new char[info->len + 1];
457 | } else {
458 | log_e("request for buffer but transfer already running. dropping client %i multi frame transfer", client->id());
459 | client->printf("%sservice currently unavailable", MESSAGE_HEADER);
460 | client->close();
461 | return;
462 | }
463 | }
464 |
465 | log_d("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT) ? "text" : "binary", info->index, info->index + len);
466 | //move the data to the buffer
467 | memcpy(buffer + info->index, data, len);
468 | log_d("Copied %i bytes to buffer at pos %llu", len, info->index);
469 |
470 | if ((info->index + len) == info->len) {
471 | log_d("ws[%s][%u] frame[%u] end[%llu]", server->url(), client->id(), info->num, info->len);
472 | if (info->final) {
473 | log_d("ws[%s][%u] %s-message end", server->url(), client->id(), (info->message_opcode == WS_TEXT) ? "text" : "binary");
474 |
475 | //we should have the complete message now stored in buffer
476 | buffer[info->len] = 0;
477 | log_d("complete multi frame request: %s", reinterpret_cast(buffer));
478 |
479 | char* pch = strtok(buffer, "\n");
480 | if (!strcmp("filetoplaylist", pch) || !strcmp("_filetoplaylist", pch)) {
481 | log_d("multi frame playlist");
482 | const bool startnow = (pch[0] == '_');
483 | const uint32_t previousSize = playList.size();
484 | pch = strtok(NULL, "\n");
485 | while (pch) {
486 | log_d("argument: %s", pch);
487 | playList.add({ HTTP_FILE, "", pch, 0 });
488 | pch = strtok(NULL, "\n");
489 | }
490 | delete[] buffer;
491 | buffer = nullptr;
492 |
493 | log_d("Added %i items to playlist", playList.size() - previousSize);
494 |
495 | client->printf("%sAdded %i items to playlist", MESSAGE_HEADER, playList.size() - previousSize);
496 |
497 | if (!playList.isUpdated) return;
498 |
499 | if (startnow) {
500 | if (audio.isRunning()) muteVolumeAndStopSong();
501 | currentItem = previousSize - 1;
502 | playerStatus = PLAYING;
503 | return;
504 | }
505 | // start playing at the correct position if not already playing
506 | if (!audio.isRunning() && PAUSED != playerStatus) {
507 | currentItem = previousSize - 1;
508 | playerStatus = PLAYING;
509 | }
510 | }
511 | }
512 | }
513 | }
514 | }
515 | }
516 |
517 | const char* HEADER_MODIFIED_SINCE = "If-Modified-Since";
518 |
519 | static inline __attribute__((always_inline)) bool htmlUnmodified(const AsyncWebServerRequest* request, const char* date) {
520 | return request->hasHeader(HEADER_MODIFIED_SINCE) && request->header(HEADER_MODIFIED_SINCE).equals(date);
521 | }
522 |
523 | void setup() {
524 | audio.setVolume(0);
525 |
526 | btStop();
527 |
528 | #if defined(M5STACK_NODE)
529 | M5.begin(true, false);
530 | M5.Lcd.setBrightness(15);
531 | M5.Lcd.setTextColor(TFT_LIGHTGREY, TFT_BLACK);
532 | M5.Lcd.setTextDatum(TC_DATUM); // TC = Top Center
533 | M5.Lcd.setFreeFont(FSS12);
534 | const uint16_t ypos = M5.Lcd.fontHeight(GFXFF);
535 | M5.Lcd.drawString("-eStreamPlayer32-", M5.Lcd.width() / 2, 0);
536 | #endif //M5STACK_NODE
537 |
538 | if (psramInit()) {
539 | log_i("%.2fMB PSRAM free.", ESP.getFreePsram() / (1024.0 * 1024));
540 | }
541 |
542 | /* check if a ffat partition is defined and halt the system if it is not defined*/
543 | if (!esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "ffat")) {
544 | log_e("FATAL ERROR! No FFat partition defined. System is halted.\nCheck 'Tools>Partition Scheme' in the Arduino IDE and select a partition table with a FFat partition.");
545 |
546 | #if defined(M5STACK_NODE)
547 | M5_displayItemName({ HTTP_FAVORITE, "ERROR no FFat partition!", "", 0 });
548 | #endif //M5STACK_NODE
549 |
550 | while (true) delay(1000); /* system is halted */
551 | }
552 |
553 | /* partition is defined - try to mount it */
554 | if (FFat.begin(0, "", 2)) // see: https://github.com/lorol/arduino-esp32fs-plugin#notes-for-fatfs
555 | log_i("FFat mounted.");
556 |
557 | /* partition is present, but does not mount so now we just format it */
558 | else {
559 | log_i("Formatting...");
560 |
561 | #if defined(M5STACK_NODE)
562 | M5_displayItemName({ HTTP_FAVORITE, "Formatting.Please wait...", "", 0 });
563 | #endif //M5STACK_NODE
564 |
565 | if (!FFat.format(true, (char*)"ffat") || !FFat.begin(0, "", 2)) {
566 | log_e("FFat error while formatting. Halting.");
567 |
568 | #if defined(M5STACK_NODE)
569 | M5_displayItemName({ HTTP_FAVORITE, "ERROR formatting!", "", 0 });
570 | #endif //M5STACK_NODE
571 |
572 | while (true) delay(1000); /* system is halted */
573 | ;
574 | }
575 | }
576 |
577 | log_i("Found %i presets", sizeof(preset) / sizeof(source));
578 |
579 | #if defined(A1S_AUDIO_KIT)
580 | log_i("Starting 'A1S_AUDIO_KIT' dac");
581 | if (!dac.begin(I2C_SDA, I2C_SCL)) {
582 | log_e("AC101 dac failed to init! Halting.");
583 | while (true) delay(1000); /* system is halted */
584 | ;
585 | }
586 | audio.i2s_mclk_pin_select(I2S_MCLK);
587 | dac.SetVolumeSpeaker(30);
588 | dac.SetVolumeHeadphone(63);
589 | #endif //A1S_AUDIO_KIT
590 |
591 | #if defined(M5STACK_NODE)
592 | M5_displayItemName({ HTTP_FAVORITE, "Connecting...", "", 0 });
593 | #endif //M5STACK_NODE
594 |
595 | if (SET_STATIC_IP && !WiFi.config(STATIC_IP, GATEWAY, SUBNET, PRIMARY_DNS, SECONDARY_DNS)) {
596 | log_e("Setting static IP failed");
597 | }
598 |
599 | WiFi.begin(SSID, PSK);
600 | WiFi.setSleep(false);
601 | WiFi.waitForConnectResult();
602 |
603 | #if defined(M5STACK_NODE)
604 | if (!WiFi.isConnected()) {
605 | M5_displayItemName({ HTTP_FAVORITE, "HALTED: No network!", "", 0 });
606 | while (true) delay(1000); /* system is halted */
607 | ;
608 | }
609 | M5_displayItemName({ HTTP_FAVORITE, "", "", 0 });
610 | M5.Lcd.setTextColor(TFT_LIGHTGREY, TFT_BLACK);
611 | M5.Lcd.setFreeFont(FF6);
612 | M5.Lcd.drawString(WiFi.localIP().toString(), M5.Lcd.width() / 2, ypos);
613 | log_i("Starting 'M5STACK_NODE' dac");
614 | if (!dac.begin(I2C_SDA, I2C_SCL)) {
615 | log_e("WM8978 dac failed to init! Halting.");
616 | M5_displayItemName({ HTTP_FAVORITE, "HALTED: No WM8978 DAC!", "", 0 });
617 | while (true) delay(1000); /* system is halted */
618 | ;
619 | }
620 | M5_displayCurrentAndTotal();
621 | M5.Lcd.display();
622 | dac.setSPKvol(0);
623 | dac.setHPvol(0, 0);
624 | audio.i2s_mclk_pin_select(I2S_MCLK);
625 | dac.setHPvol(63, 63);
626 | #endif //M5STACK_NODE
627 |
628 | #if defined(GENERIC_I2S_DAC)
629 | log_i("Starting 'GENERIC_I2S_DAC' - BCK=%i LRC=%i DOUT=%i", I2S_BCK, I2S_WS, I2S_DOUT);
630 | #endif //GENERIC_I2S_DAC
631 |
632 | if (!WiFi.isConnected()) {
633 | log_e("Could not connect to Wifi! System halted! Check 'wifi_setup.h'!");
634 | while (true) delay(1000); /* system is halted */
635 | ;
636 | }
637 |
638 | log_i("WiFi: %s", WiFi.localIP().toString().c_str());
639 |
640 | /* sync with ntp */
641 | configTzTime(TIMEZONE, NTP_POOL);
642 |
643 | struct tm timeinfo {};
644 |
645 | log_i("Waiting for NTP sync..");
646 |
647 | while (!getLocalTime(&timeinfo, 0))
648 | delay(10);
649 |
650 | time_t bootTime;
651 | time(&bootTime);
652 | static char modifiedDate[30];
653 | strftime(modifiedDate, sizeof(modifiedDate), "%a, %d %b %Y %X GMT", gmtime(&bootTime));
654 |
655 | static const char* HTML_MIMETYPE{ "text/html" };
656 | static const char* HEADER_LASTMODIFIED{ "Last-Modified" };
657 | static const char* CONTENT_ENCODING_HEADER{ "Content-Encoding" };
658 | static const char* CONTENT_ENCODING_VALUE{ "gzip" };
659 |
660 | ws.onEvent(onEvent);
661 | server.addHandler(&ws);
662 |
663 | server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
664 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
665 | AsyncWebServerResponse* response = request->beginResponse_P(200, HTML_MIMETYPE, index_htm_gz, index_htm_gz_len);
666 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
667 | response->addHeader(CONTENT_ENCODING_HEADER, CONTENT_ENCODING_VALUE);
668 | request->send(response);
669 | });
670 |
671 | server.on("/stations", HTTP_GET, [](AsyncWebServerRequest* request) {
672 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
673 | AsyncResponseStream* response = request->beginResponseStream(HTML_MIMETYPE);
674 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
675 | for (int i = 0; i < sizeof(preset) / sizeof(source); i++) {
676 | response->printf("%s\n", preset[i].name.c_str());
677 | }
678 | request->send(response);
679 | });
680 |
681 | server.on("/scripturl", HTTP_GET, [](AsyncWebServerRequest* request) {
682 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
683 | AsyncResponseStream* response = request->beginResponseStream(HTML_MIMETYPE);
684 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
685 | response->print(SCRIPT_URL);
686 | request->send(response);
687 | });
688 |
689 | static const char* SVG_MIMETYPE{ "image/svg+xml" };
690 |
691 | server.on("/radioicon.svg", HTTP_GET, [](AsyncWebServerRequest* request) {
692 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
693 | AsyncWebServerResponse* response = request->beginResponse_P(200, SVG_MIMETYPE, radioicon);
694 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
695 | request->send(response);
696 | });
697 |
698 | server.on("/playicon.svg", HTTP_GET, [](AsyncWebServerRequest* request) {
699 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
700 | AsyncWebServerResponse* response = request->beginResponse_P(200, SVG_MIMETYPE, playicon);
701 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
702 | request->send(response);
703 | });
704 |
705 | server.on("/libraryicon.svg", HTTP_GET, [](AsyncWebServerRequest* request) {
706 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
707 | AsyncWebServerResponse* response = request->beginResponse_P(200, SVG_MIMETYPE, libraryicon);
708 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
709 | request->send(response);
710 | });
711 |
712 | server.on("/favoriteicon.svg", HTTP_GET, [](AsyncWebServerRequest* request) {
713 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
714 | AsyncWebServerResponse* response = request->beginResponse_P(200, SVG_MIMETYPE, favoriteicon);
715 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
716 | request->send(response);
717 | });
718 |
719 | server.on("/streamicon.svg", HTTP_GET, [](AsyncWebServerRequest* request) {
720 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
721 | AsyncWebServerResponse* response = request->beginResponse_P(200, SVG_MIMETYPE, pasteicon);
722 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
723 | request->send(response);
724 | });
725 |
726 | server.on("/deleteicon.svg", HTTP_GET, [](AsyncWebServerRequest* request) {
727 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
728 | AsyncWebServerResponse* response = request->beginResponse_P(200, SVG_MIMETYPE, deleteicon);
729 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
730 | request->send(response);
731 | });
732 |
733 | server.on("/addfoldericon.svg", HTTP_GET, [](AsyncWebServerRequest* request) {
734 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
735 | AsyncWebServerResponse* response = request->beginResponse_P(200, SVG_MIMETYPE, addfoldericon);
736 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
737 | request->send(response);
738 | });
739 |
740 | server.on("/emptyicon.svg", HTTP_GET, [](AsyncWebServerRequest* request) {
741 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
742 | AsyncWebServerResponse* response = request->beginResponse_P(200, SVG_MIMETYPE, emptyicon);
743 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
744 | request->send(response);
745 | });
746 |
747 | server.on("/starticon.svg", HTTP_GET, [](AsyncWebServerRequest* request) {
748 | if (htmlUnmodified(request, modifiedDate)) return request->send(304);
749 | AsyncWebServerResponse* response = request->beginResponse_P(200, SVG_MIMETYPE, starticon);
750 | response->addHeader(HEADER_LASTMODIFIED, modifiedDate);
751 | request->send(response);
752 | });
753 |
754 | server.onNotFound([](AsyncWebServerRequest* request) {
755 | log_e("404 - Not found: 'http://%s%s'", request->host().c_str(), request->url().c_str());
756 | request->send(404);
757 | });
758 |
759 | DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
760 |
761 | server.begin();
762 | log_i("Ready to rock!");
763 |
764 | audio.setPinout(I2S_BCK, I2S_WS, I2S_DOUT);
765 | audio.setVolume(I2S_INITIAL_VOLUME);
766 | }
767 |
768 | String& favoritesToString(String& s) {
769 | File root = FFat.open("/");
770 | s = "";
771 | if (!root || !root.isDirectory()) {
772 | log_e("ERROR - root folder problem");
773 | return s;
774 | }
775 | s = "favorites\n";
776 | File file = root.openNextFile();
777 | while (file) {
778 | if (!file.isDirectory()) {
779 | s.concat(file.name());
780 | s.concat("\n");
781 | }
782 | file = root.openNextFile();
783 | }
784 | return s;
785 | }
786 |
787 | bool startPlaylistItem(const playListItem& item) {
788 | switch (item.type) {
789 | case HTTP_FILE:
790 | log_d("STARTING file: %s", item.url.c_str());
791 | audio_showstation(item.url.substring(item.url.lastIndexOf("/") + 1).c_str());
792 | audio_showstreamtitle(item.url.substring(0, item.url.lastIndexOf("/")).c_str());
793 | audio.connecttohost(urlEncode(item.url).c_str());
794 | break;
795 | case HTTP_STREAM:
796 | log_d("STARTING stream: %s", item.url.c_str());
797 | audio_showstation(item.url.substring(item.url.lastIndexOf("/") + 1).c_str());
798 | audio_showstreamtitle("");
799 | audio.connecttohost(urlEncode(item.url).c_str());
800 | break;
801 | case HTTP_PRESET:
802 | log_d("STARTING preset: %s -> %s", preset[item.index].name.c_str(), preset[item.index].url.c_str());
803 | audio_showstreamtitle("");
804 | audio_showstation(preset[item.index].name.c_str());
805 | audio.connecttohost(urlEncode(preset[item.index].url).c_str());
806 | break;
807 | case HTTP_FAVORITE:
808 | log_d("STARTING favorite: %s -> %s", item.name.c_str(), item.url.c_str());
809 | audio_showstation(item.name.c_str());
810 | audio_showstreamtitle("");
811 | audio.connecttohost(urlEncode(item.url).c_str());
812 | break;
813 | case SDCARD_FILE:
814 | log_d("STARTING sd file: %s", item.url.c_str());
815 | audio.connecttoSD(item.url.c_str());
816 | break;
817 | default: log_e("Unhandled item.type.");
818 | }
819 | return audio.isRunning();
820 | }
821 |
822 | bool saveItemToFavorites(const playListItem& item, const String& filename) {
823 | switch (item.type) {
824 | case HTTP_FILE:
825 | log_d("file (wont save)%s", item.url.c_str());
826 | return false;
827 | break;
828 | case HTTP_PRESET:
829 | log_d("preset (wont save) %s %s", preset[item.index].name.c_str(), preset[item.index].url.c_str());
830 | return false;
831 | break;
832 | case HTTP_STREAM:
833 | case HTTP_FAVORITE:
834 | {
835 | if (filename.equals("")) {
836 | log_e("Could not save current item. No filename given!");
837 | return false;
838 | }
839 | log_d("saving stream: %s -> %s", filename.c_str(), item.url.c_str());
840 | File file = FFat.open("/" + filename, FILE_WRITE);
841 | if (!file) {
842 | log_e("failed to open file for writing");
843 | return false;
844 | }
845 | bool result = file.print(item.url.c_str());
846 | file.close();
847 | log_d("%s writing to '%s'", result ? "ok" : "WARNING - failed", filename);
848 | return result;
849 | }
850 | break;
851 | default:
852 | {
853 | log_w("Unhandled item.type.");
854 | return false;
855 | }
856 | }
857 | }
858 |
859 | void handlePastedUrl() {
860 |
861 | if (playList.size() > PLAYLIST_MAX_ITEMS - 1) {
862 | char buffer[50];
863 | snprintf(buffer, sizeof(buffer), "%sPlaylist is full.", MESSAGE_HEADER);
864 | ws.text(newUrl.clientId, buffer);
865 | return;
866 | }
867 |
868 | log_i("STARTING new url: %s with %i items in playList", newUrl.url.c_str(), playList.size());
869 | muteVolumeAndStopSong();
870 | audio_showstreamtitle("starting new stream");
871 | audio_showstation("");
872 | const playListItem item{ HTTP_STREAM, newUrl.url, newUrl.url, 0 };
873 | if (startPlaylistItem(item)) {
874 | log_d("url started successful");
875 | playList.add(item);
876 |
877 | currentItem = playList.size() - 1;
878 | playerStatus = PLAYING;
879 | audio_showstation(newUrl.url.c_str());
880 |
881 | #if defined(M5STACK_NODE)
882 | M5_displayItemName({ HTTP_STREAM, "", newUrl.url, 0 });
883 | #endif //M5STACK_NODE
884 |
885 | } else {
886 | char buff[100];
887 | snprintf(buff, sizeof(buff), "%sFailed to play stream", MESSAGE_HEADER);
888 | ws.text(newUrl.clientId, buff);
889 | playListHasEnded();
890 | log_i("url failed to start");
891 | }
892 | }
893 |
894 | void handleFavoriteToPlaylist(const String& filename, const bool startNow) {
895 | File file = FFat.open("/" + filename);
896 | String url;
897 | if (file) {
898 | while (file.available() && (file.peek() != '\n') && url.length() < 1024) /* only read the first line and limit the size of the resulting string - unknown/leftover files might contain garbage*/
899 | url += (char)file.read();
900 | file.close();
901 | } else {
902 | log_e("Could not open %s", filename.c_str());
903 | ws.printfAll("%sCould not add '%s' to playlist", MESSAGE_HEADER, filename.c_str());
904 | return;
905 | }
906 | playList.add({ HTTP_FAVORITE, filename, url, 0 });
907 |
908 | if (!playList.isUpdated) return;
909 |
910 | log_d("favorite to playlist: %s -> %s", filename.c_str(), url.c_str());
911 | ws.printfAll("%sAdded '%s' to playlist", MESSAGE_HEADER, filename.c_str());
912 | if (startNow) {
913 | if (audio.isRunning()) muteVolumeAndStopSong();
914 | currentItem = playList.size() - 2;
915 | playerStatus = PLAYING;
916 | return;
917 | }
918 | if (!audio.isRunning() && PAUSED != playerStatus) {
919 | currentItem = playList.size() - 2;
920 | playerStatus = PLAYING;
921 | }
922 | }
923 |
924 | void handleCurrentToFavorites(const String& filename, const uint32_t clientId) {
925 | playListItem item;
926 | playList.get(currentItem, item);
927 |
928 | if (saveItemToFavorites(item, filename)) {
929 | ws.printfAll("%sAdded '%s' to favorites!", MESSAGE_HEADER, filename.c_str());
930 | updateFavoritesOnClients();
931 | } else
932 | ws.printf(clientId, "%sSaving '%s' failed!", MESSAGE_HEADER, filename.c_str());
933 | }
934 |
935 | void startCurrentItem() {
936 | playListItem item;
937 | playList.get(currentItem, item);
938 |
939 | #if defined(M5STACK_NODE)
940 | M5_displayItemName(item);
941 | M5_displayCurrentAndTotal();
942 | #endif //M5STACK_NODE
943 |
944 | log_d("Starting playlist item: %i", currentItem);
945 |
946 | if (!startPlaylistItem(item))
947 | ws.printfAll("error - could not start %s", (item.type == HTTP_PRESET) ? preset[item.index].url.c_str() : item.url.c_str());
948 |
949 | updateHighlightedItemOnClients();
950 | }
951 |
952 | void loop() {
953 |
954 | #if defined(M5STACK_NODE)
955 | M5.update();
956 |
957 | if (M5.BtnA.wasReleasefor(10)) {
958 | static bool speakerstate{ false };
959 | speakerstate = !speakerstate;
960 | dac.setSPKvol(speakerstate ? 40 : 0);
961 | log_d("Speaker %s", speakerstate ? "on" : "off");
962 | }
963 | #endif //M5STACK_NODE
964 |
965 | audio.loop();
966 |
967 | ws.cleanupClients();
968 |
969 | if (playList.isUpdated) {
970 | {
971 | String s;
972 | ws.textAll(playList.toString(s));
973 | }
974 |
975 | #if defined(M5STACK_NODE)
976 | M5_displayCurrentAndTotal();
977 | #endif
978 |
979 | log_i("Playlist updated. %i items. Free mem: %i", playList.size(), ESP.getFreeHeap());
980 |
981 | updateHighlightedItemOnClients();
982 |
983 | playList.isUpdated = false;
984 | }
985 |
986 | if (newUrl.waiting) {
987 | handlePastedUrl();
988 | newUrl.waiting = false;
989 | }
990 |
991 | if (!audio.isRunning() && playList.size() && PLAYING == playerStatus) {
992 | if (currentItem < playList.size() - 1) {
993 | currentItem++;
994 | startCurrentItem();
995 | } else
996 | playListHasEnded();
997 | }
998 | }
999 |
--------------------------------------------------------------------------------
/icons.h:
--------------------------------------------------------------------------------
1 | const char* radioicon = R"====()====";
2 | const char* playicon = R"====()====";
3 | const char* libraryicon = R"====()====";
4 | const char* favoriteicon = R"====()====";
5 | const char* pasteicon = R"====()====";
6 | const char* deleteicon = R"====()====";
7 | const char* addfoldericon = R"====()====";
8 | const char* emptyicon = R"====()====";
9 | const char* starticon = R"====()====";
10 |
11 | /*
12 | const char* deleteicon = R"====(heregoesthestringliteral)====";
13 | const char* deleteicon = R"====()====";
14 | const char* deleteicon = R"====()====";
15 | const char* deleteicon = R"====()====";
16 | const char* deleteicon = R"====()====";
17 | */
18 |
--------------------------------------------------------------------------------
/img/favorites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CelliesProjects/eStreamPlayer32/d03c8f952f7cc958f9f42eb3248a2a9652bd44b9/img/favorites.png
--------------------------------------------------------------------------------
/img/library.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CelliesProjects/eStreamPlayer32/d03c8f952f7cc958f9f42eb3248a2a9652bd44b9/img/library.png
--------------------------------------------------------------------------------
/img/overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CelliesProjects/eStreamPlayer32/d03c8f952f7cc958f9f42eb3248a2a9652bd44b9/img/overlay.png
--------------------------------------------------------------------------------
/img/webradio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CelliesProjects/eStreamPlayer32/d03c8f952f7cc958f9f42eb3248a2a9652bd44b9/img/webradio.png
--------------------------------------------------------------------------------
/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 | eStreamPlayer32
14 |
15 |
16 |
17 |
18 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
Waiting for playlist...
220 |
221 |
222 |
connect to library...
223 |
224 |
225 |
226 |
227 |
Waiting for favorites...
228 |
229 |
233 |
234 |
235 |
eStreamPlayer32
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 | message overlay
245 |
614 |
615 |
616 |
--------------------------------------------------------------------------------
/index.htm.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CelliesProjects/eStreamPlayer32/d03c8f952f7cc958f9f42eb3248a2a9652bd44b9/index.htm.gz
--------------------------------------------------------------------------------
/index_htm_gz.h:
--------------------------------------------------------------------------------
1 | const uint8_t index_htm_gz[] = {
2 | 0x1f, 0x8b, 0x08, 0x08, 0x1b, 0x4b, 0x5b, 0x60, 0x00, 0x03, 0x69, 0x6e,
3 | 0x64, 0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d, 0x00, 0xc4, 0x1b, 0x6b, 0x73,
4 | 0x9b, 0x48, 0xf2, 0xbb, 0x7f, 0xc5, 0x84, 0xec, 0x45, 0x62, 0x23, 0x10,
5 | 0xe8, 0x61, 0xcb, 0x92, 0x71, 0x2a, 0x71, 0xbc, 0x17, 0xd7, 0x39, 0x71,
6 | 0xca, 0xce, 0x65, 0xeb, 0x2a, 0x97, 0xda, 0x42, 0x30, 0x12, 0xc4, 0x08,
7 | 0xb4, 0x80, 0x24, 0x3b, 0x8e, 0xfe, 0xfb, 0x75, 0xcf, 0x03, 0x06, 0x09,
8 | 0xd9, 0xde, 0x64, 0x77, 0x2f, 0x0f, 0x01, 0x33, 0x3d, 0x3d, 0x3d, 0xfd,
9 | 0xee, 0x61, 0x38, 0x7a, 0xe2, 0x27, 0x5e, 0x7e, 0x3b, 0xa7, 0x24, 0xc8,
10 | 0x67, 0xd1, 0xf1, 0xde, 0x11, 0x5e, 0x48, 0xe4, 0xc6, 0x53, 0x47, 0xa3,
11 | 0xb1, 0x86, 0x0d, 0xd4, 0xf5, 0xe1, 0x92, 0x79, 0x69, 0x38, 0xcf, 0x8f,
12 | 0xf7, 0xda, 0xed, 0x8c, 0xe6, 0x79, 0x18, 0x4f, 0xc9, 0x24, 0x49, 0x89,
13 | 0x4f, 0x97, 0x6d, 0x9f, 0x8e, 0x17, 0x53, 0xf2, 0x73, 0x7b, 0xcf, 0x4b,
14 | 0xe2, 0x2c, 0x27, 0xec, 0x31, 0x9c, 0x3b, 0x9a, 0x7d, 0xd8, 0x31, 0xed,
15 | 0xfd, 0x81, 0x69, 0x99, 0xb6, 0xd5, 0xd7, 0x46, 0x84, 0xb4, 0xdb, 0x5e,
16 | 0x00, 0x88, 0x29, 0xc9, 0x83, 0x30, 0x23, 0x79, 0x02, 0x57, 0x4a, 0x68,
17 | 0x36, 0xef, 0x76, 0x48, 0x38, 0x67, 0xe8, 0x52, 0x3a, 0x4b, 0x72, 0x4a,
18 | 0xd2, 0x45, 0x1c, 0xe3, 0x0c, 0xc9, 0x84, 0x83, 0xce, 0xdd, 0x29, 0xdd,
19 | 0x3b, 0x6a, 0x4b, 0x12, 0x8e, 0x66, 0x34, 0x77, 0x09, 0xe0, 0x4a, 0x81,
20 | 0x14, 0x47, 0x5b, 0xe4, 0x13, 0x63, 0xa0, 0xc9, 0xe6, 0xd8, 0x9d, 0x51,
21 | 0x47, 0x5b, 0x86, 0x74, 0x35, 0x4f, 0xd2, 0x5c, 0x23, 0x40, 0x54, 0x4e,
22 | 0x63, 0x00, 0x9b, 0x85, 0x71, 0x38, 0x73, 0x23, 0x63, 0x11, 0xb6, 0x56,
23 | 0xa1, 0x9f, 0x07, 0x0e, 0xd0, 0x1e, 0x7a, 0xd4, 0x60, 0x0f, 0x2d, 0xe8,
24 | 0xcc, 0x43, 0xe8, 0xcd, 0x3c, 0x37, 0xa2, 0x8e, 0x6d, 0x5a, 0xad, 0x45,
25 | 0x46, 0x53, 0xf6, 0xe8, 0x8e, 0xa1, 0x25, 0x4e, 0x34, 0xd2, 0x86, 0x39,
26 | 0xa2, 0x30, 0xbe, 0x06, 0x32, 0x23, 0x47, 0x0b, 0x01, 0xb3, 0x46, 0x82,
27 | 0x94, 0x4e, 0x1c, 0xcd, 0x77, 0x73, 0x77, 0x38, 0x1a, 0xbb, 0x19, 0xdd,
28 | 0xef, 0xb5, 0xc2, 0x8f, 0xaf, 0x2e, 0x2e, 0x57, 0xd6, 0xbf, 0xfe, 0x39,
29 | 0x4d, 0x1c, 0xed, 0xf8, 0xe8, 0x89, 0x61, 0xcc, 0x53, 0xba, 0x04, 0x1a,
30 | 0xc8, 0xc4, 0x5d, 0xe2, 0x28, 0x18, 0xff, 0xfb, 0x82, 0x66, 0x79, 0x66,
31 | 0x18, 0x12, 0x23, 0x47, 0x13, 0xe4, 0xf9, 0x3c, 0x1b, 0xb6, 0xdb, 0x13,
32 | 0xa0, 0x39, 0x33, 0xa7, 0x49, 0x32, 0x8d, 0xa8, 0x3b, 0x0f, 0x33, 0xd3,
33 | 0x4b, 0x66, 0x6d, 0x2f, 0xcb, 0x5e, 0x4c, 0xdc, 0x59, 0x18, 0xdd, 0x3a,
34 | 0x97, 0xc9, 0x38, 0xc9, 0x81, 0x20, 0x46, 0x47, 0x96, 0xdf, 0x46, 0x34,
35 | 0x0b, 0x28, 0xcd, 0xb5, 0x42, 0x4c, 0x24, 0x4b, 0xbd, 0x12, 0x9d, 0x97,
36 | 0xf8, 0xd4, 0xfc, 0x02, 0x53, 0xa6, 0xb7, 0x0c, 0x13, 0xbf, 0x35, 0xba,
37 | 0x66, 0xcf, 0xb4, 0xcd, 0x2f, 0x19, 0xd0, 0x58, 0xb2, 0x36, 0x0f, 0xf3,
38 | 0x88, 0x1e, 0xd3, 0xab, 0x3c, 0xa5, 0xee, 0xec, 0x7d, 0xe4, 0xde, 0xd2,
39 | 0xb4, 0xdb, 0x39, 0x6a, 0xf3, 0xe6, 0x0a, 0x8b, 0x7d, 0xca, 0x07, 0x85,
40 | 0xc8, 0x86, 0x82, 0xcb, 0x1b, 0x23, 0x49, 0x08, 0xed, 0xe9, 0xc4, 0xf5,
41 | 0xe8, 0x86, 0x80, 0xdc, 0x45, 0x1e, 0x24, 0xa9, 0x32, 0xf0, 0x84, 0x46,
42 | 0x51, 0x48, 0xcb, 0x25, 0x1c, 0x3f, 0x99, 0x2c, 0x62, 0x0f, 0xb1, 0x37,
43 | 0xdd, 0xd6, 0x58, 0xbf, 0xd3, 0xe4, 0xa3, 0xe6, 0x38, 0xa8, 0xaf, 0xa0,
44 | 0x1b, 0x3e, 0x9d, 0x84, 0x31, 0x7d, 0xf6, 0x8c, 0x5f, 0x4d, 0x77, 0xe6,
45 | 0xbf, 0xe0, 0xb7, 0xcd, 0x4f, 0x9f, 0x61, 0xc8, 0x50, 0x5b, 0xc4, 0xfc,
46 | 0xd9, 0xd7, 0x9e, 0xc8, 0x31, 0xb3, 0xc4, 0x5f, 0x44, 0x30, 0x86, 0x5f,
47 | 0x4d, 0x7a, 0x83, 0x5a, 0x92, 0xbd, 0xa8, 0x3e, 0x3a, 0xe3, 0xa6, 0x3e,
48 | 0x74, 0xcd, 0x4b, 0x0a, 0xd4, 0xc5, 0xd4, 0x43, 0x6d, 0xff, 0x95, 0x8e,
49 | 0xaf, 0x12, 0xef, 0x1a, 0xf4, 0x0d, 0xfa, 0xd6, 0x4d, 0x54, 0xcb, 0x56,
50 | 0x41, 0xa0, 0x7e, 0x27, 0x6f, 0x89, 0xdb, 0x1c, 0xb7, 0xbc, 0x96, 0xaf,
51 | 0xb4, 0x44, 0x9c, 0xfe, 0xa5, 0x9b, 0x12, 0xcf, 0x01, 0x63, 0x5b, 0xcc,
52 | 0x60, 0xbd, 0xa6, 0x07, 0x5c, 0xca, 0xe9, 0x29, 0xaa, 0x45, 0x53, 0x3b,
53 | 0x59, 0x64, 0x79, 0x32, 0x63, 0x0f, 0x9a, 0x3e, 0x4a, 0x69, 0xbe, 0x48,
54 | 0x63, 0xe2, 0x99, 0xa8, 0x93, 0x4a, 0x17, 0xe0, 0x79, 0x62, 0xe3, 0xbf,
55 | 0xb1, 0xde, 0xf2, 0xd6, 0x88, 0x8f, 0x3a, 0x77, 0xcc, 0xda, 0x86, 0xd0,
56 | 0x08, 0x0c, 0x4d, 0x66, 0x6e, 0x1e, 0x7a, 0x17, 0x73, 0x1a, 0x0f, 0x9f,
57 | 0x58, 0xad, 0x54, 0x52, 0x7f, 0x86, 0x22, 0x58, 0xba, 0xd1, 0xd0, 0xa6,
58 | 0xdd, 0xd6, 0xcc, 0xbd, 0xb9, 0xdc, 0xea, 0xe8, 0xd2, 0x5e, 0x09, 0xfe,
59 | 0x9a, 0x7a, 0xee, 0xed, 0xd0, 0x36, 0xfb, 0xad, 0x3c, 0x9c, 0xd1, 0x64,
60 | 0x51, 0x82, 0x75, 0x68, 0x77, 0x3d, 0xf2, 0xbf, 0x7d, 0x6b, 0xfa, 0xce,
61 | 0xdd, 0x5a, 0x1f, 0x81, 0xc1, 0x36, 0x91, 0x8a, 0x09, 0x08, 0x99, 0x50,
62 | 0x1d, 0x59, 0xf2, 0x69, 0xf2, 0xd9, 0xa9, 0xe3, 0xba, 0x0f, 0x1d, 0x2f,
63 | 0xf0, 0x67, 0x48, 0xe1, 0x67, 0x84, 0xa0, 0xe6, 0x22, 0x8d, 0x9c, 0x71,
64 | 0x8b, 0xdd, 0x16, 0x73, 0xbf, 0xcc, 0x73, 0x3a, 0x9b, 0x83, 0x00, 0x2c,
65 | 0xd9, 0xe1, 0xfa, 0xb7, 0x57, 0x39, 0x70, 0xca, 0x29, 0x04, 0x60, 0x9e,
66 | 0x5c, 0xbc, 0x7b, 0x77, 0x7a, 0xf2, 0xe1, 0xec, 0xdd, 0x3f, 0x39, 0xd0,
67 | 0x3c, 0x05, 0x63, 0xf0, 0x92, 0xc8, 0x89, 0x17, 0x51, 0x34, 0x42, 0x8a,
68 | 0x82, 0xd6, 0xd4, 0x61, 0x12, 0x0a, 0x1d, 0xe0, 0xcc, 0x17, 0xfc, 0xb9,
69 | 0xde, 0x62, 0x7d, 0x44, 0x67, 0x8c, 0xf9, 0x7e, 0xb8, 0x04, 0xa6, 0x5f,
70 | 0x9b, 0xae, 0xef, 0x33, 0x36, 0x9f, 0x87, 0x19, 0x68, 0x24, 0x4d, 0x9b,
71 | 0x5a, 0x02, 0x9c, 0xd4, 0x4a, 0x29, 0xbb, 0xfa, 0xdd, 0xd4, 0x4c, 0x62,
72 | 0x6c, 0x85, 0xfb, 0xb5, 0xde, 0xaa, 0x1b, 0xe3, 0x45, 0x49, 0x46, 0xb7,
73 | 0x07, 0xb1, 0xe6, 0x7b, 0x46, 0x15, 0x7a, 0x56, 0x33, 0xb4, 0xe8, 0xdb,
74 | 0x3d, 0x7e, 0x46, 0xb3, 0x0c, 0x7c, 0xe4, 0xf6, 0x60, 0xd1, 0xb1, 0x7b,
75 | 0x24, 0x4d, 0x53, 0x30, 0xc3, 0xad, 0x71, 0xac, 0x99, 0x8f, 0x62, 0x3c,
76 | 0xde, 0x1c, 0xe8, 0x6c, 0xe3, 0x32, 0xc7, 0x61, 0xec, 0x37, 0xaf, 0x75,
77 | 0x29, 0xb9, 0x59, 0xb2, 0xa4, 0x9b, 0x63, 0x6a, 0x5a, 0xab, 0xc3, 0xfc,
78 | 0x30, 0x9b, 0xbb, 0xb9, 0x17, 0x30, 0x10, 0x18, 0x50, 0x79, 0xae, 0x82,
79 | 0xa2, 0x1c, 0x9c, 0x82, 0x6e, 0xb0, 0xb0, 0xc0, 0x89, 0xe9, 0x8a, 0x14,
80 | 0x7a, 0xd2, 0x9c, 0xa2, 0x8a, 0xb5, 0xbc, 0x6f, 0xdf, 0x3e, 0x7d, 0xd6,
81 | 0x5b, 0xe3, 0x6f, 0xdf, 0x36, 0xb0, 0x35, 0xa3, 0x0a, 0xdf, 0x75, 0xbd,
82 | 0x05, 0x23, 0x98, 0x39, 0x7d, 0xfb, 0xe6, 0xf2, 0x9b, 0x97, 0x51, 0xa4,
83 | 0x3f, 0x7b, 0x86, 0x61, 0x2d, 0x01, 0xe7, 0xc0, 0x9a, 0x9a, 0x5a, 0xad,
84 | 0x57, 0xd0, 0x5a, 0x9a, 0xcb, 0x75, 0xd7, 0x10, 0xbd, 0x5a, 0x8b, 0xcd,
85 | 0xaf, 0x33, 0x85, 0xf4, 0x9d, 0xa0, 0x45, 0x1d, 0x88, 0x56, 0x1f, 0xb8,
86 | 0x49, 0x35, 0x15, 0xd7, 0xf1, 0x63, 0xb3, 0xca, 0xd6, 0x24, 0x36, 0x84,
87 | 0xb9, 0xca, 0x89, 0x51, 0xef, 0xad, 0x96, 0x6f, 0x72, 0xd5, 0x63, 0x8f,
88 | 0xf6, 0x1a, 0xfa, 0x36, 0xac, 0x5a, 0x1f, 0x05, 0x42, 0xa9, 0x1d, 0x85,
89 | 0x28, 0x0f, 0x02, 0x4f, 0x2a, 0x89, 0xa5, 0x3f, 0xca, 0x1a, 0x8e, 0xbf,
90 | 0x20, 0x6c, 0x5a, 0x9a, 0x6c, 0x50, 0xdc, 0x42, 0x6b, 0xad, 0xb5, 0x5f,
91 | 0xbc, 0x3f, 0x7d, 0xc7, 0xfa, 0xb6, 0x5c, 0x84, 0xe0, 0x6c, 0x24, 0x0c,
92 | 0x55, 0x1f, 0xf9, 0x66, 0x98, 0x15, 0x24, 0x80, 0x6f, 0x19, 0x33, 0xc3,
93 | 0xdf, 0x90, 0xba, 0xaf, 0xaf, 0x5b, 0x81, 0xb4, 0xc8, 0x72, 0xc9, 0x9e,
94 | 0x7e, 0x17, 0x4e, 0x9a, 0x9b, 0xcb, 0x0e, 0x98, 0x4f, 0x69, 0x85, 0xfa,
95 | 0x0e, 0xe2, 0x4e, 0xce, 0x2f, 0xae, 0x4e, 0x5f, 0x6f, 0xcd, 0x11, 0x49,
96 | 0x3f, 0xa0, 0xeb, 0x23, 0x1a, 0x65, 0xf4, 0x6e, 0xd7, 0xf0, 0xc2, 0x93,
97 | 0x95, 0x6b, 0x51, 0x55, 0x12, 0x56, 0x84, 0xb1, 0xdb, 0xf1, 0xd8, 0x05,
98 | 0x64, 0x09, 0x58, 0xb2, 0x24, 0x86, 0x67, 0x7e, 0x03, 0x2d, 0x2b, 0x37,
99 | 0x3b, 0x01, 0xa2, 0xb1, 0x4d, 0xde, 0xd6, 0x2c, 0x19, 0xd5, 0xff, 0x0b,
100 | 0x38, 0xee, 0x1f, 0x15, 0xa3, 0xf0, 0x6e, 0x42, 0x8e, 0xf7, 0x2c, 0x9b,
101 | 0xab, 0x3d, 0x75, 0x14, 0xc1, 0x49, 0x85, 0xfb, 0xf9, 0xad, 0x9b, 0x83,
102 | 0xd8, 0x93, 0x55, 0x53, 0xe9, 0x64, 0x41, 0xa7, 0x4e, 0xcc, 0xfa, 0xa8,
103 | 0xde, 0x6a, 0x6a, 0x40, 0x9f, 0x3f, 0x07, 0x04, 0xcc, 0x3b, 0x3f, 0xb1,
104 | 0x40, 0xc8, 0xf4, 0x78, 0x6a, 0xd6, 0x45, 0xbb, 0x17, 0xf5, 0xcd, 0x43,
105 | 0xaa, 0xaf, 0xb9, 0x66, 0x08, 0x9f, 0x59, 0xf1, 0x2d, 0x3f, 0xca, 0xb8,
106 | 0xc2, 0x41, 0x73, 0xa7, 0x34, 0x36, 0x31, 0x63, 0xe4, 0x4c, 0xf2, 0x50,
107 | 0xea, 0xb2, 0x5f, 0x1f, 0x79, 0xac, 0xcb, 0xe1, 0x10, 0x5b, 0x2c, 0xf6,
108 | 0x84, 0xf6, 0x32, 0xff, 0xfc, 0xa7, 0x52, 0x28, 0x02, 0x81, 0xa0, 0xaf,
109 | 0x56, 0xb8, 0x1c, 0x44, 0x47, 0x3e, 0xd9, 0x8e, 0xc3, 0xe3, 0x82, 0x9a,
110 | 0x75, 0x3c, 0x7b, 0x56, 0x38, 0xe6, 0xe6, 0x13, 0x5b, 0xb8, 0xe9, 0x8c,
111 | 0xc6, 0x7e, 0x85, 0x50, 0x30, 0xb3, 0x40, 0xe7, 0x39, 0xce, 0x8f, 0xd1,
112 | 0x8c, 0x98, 0x15, 0x82, 0x03, 0x36, 0x15, 0xcc, 0x00, 0xe9, 0x45, 0x9a,
113 | 0xac, 0xb4, 0xb3, 0x77, 0x1f, 0x5f, 0x9e, 0x9f, 0xbd, 0xfe, 0xed, 0xea,
114 | 0xc3, 0xcb, 0x0f, 0xa7, 0xbf, 0x9d, 0x5e, 0x5e, 0x92, 0x21, 0x79, 0xef,
115 | 0x2e, 0x32, 0x2c, 0x2c, 0xa0, 0xf6, 0x28, 0xd4, 0x87, 0xac, 0xe8, 0x38,
116 | 0xe3, 0x38, 0xd7, 0x9c, 0xe4, 0x0d, 0xd7, 0xc0, 0xd3, 0xcf, 0x32, 0xab,
117 | 0x29, 0xf2, 0x4f, 0xf7, 0xd9, 0xb3, 0xa6, 0xeb, 0x40, 0x56, 0xa5, 0x63,
118 | 0xa2, 0x61, 0xb5, 0x82, 0x67, 0xcf, 0x02, 0xe1, 0x6e, 0x71, 0xcc, 0x5a,
119 | 0x46, 0xc2, 0x49, 0x0a, 0xf9, 0xb9, 0xea, 0x5d, 0x15, 0x40, 0x60, 0xa6,
120 | 0xc8, 0xf7, 0x5c, 0xee, 0x0a, 0x11, 0x77, 0x8d, 0x43, 0x5e, 0xb7, 0xaa,
121 | 0xfd, 0x1b, 0x34, 0xd6, 0x00, 0x14, 0x3c, 0xbb, 0x0f, 0x6a, 0x4b, 0xd7,
122 | 0xb7, 0x41, 0x36, 0x54, 0x8d, 0x03, 0x48, 0x69, 0xa1, 0x87, 0x75, 0x15,
123 | 0x3f, 0x56, 0x9f, 0xa6, 0xb9, 0xcc, 0x8b, 0x6f, 0x3a, 0x75, 0x97, 0xb9,
124 | 0xcf, 0x8d, 0x41, 0xbc, 0x45, 0xf4, 0x9d, 0xbe, 0xde, 0xf6, 0xb5, 0x2e,
125 | 0x64, 0x9e, 0x4a, 0xbd, 0x02, 0xd5, 0x15, 0x91, 0x75, 0xce, 0x6a, 0xb5,
126 | 0x32, 0x57, 0xdd, 0xcc, 0x0b, 0x92, 0x24, 0xe2, 0x35, 0x53, 0x90, 0xac,
127 | 0xf2, 0x84, 0xff, 0xfe, 0xf6, 0x25, 0xfb, 0x2d, 0x77, 0xc7, 0xa0, 0xb2,
128 | 0xd9, 0x9c, 0xb0, 0xc2, 0x8b, 0x15, 0x4e, 0xc7, 0x7b, 0x3f, 0xdf, 0x8d,
129 | 0x93, 0x1b, 0x23, 0x0b, 0xbf, 0x02, 0xab, 0x86, 0xe3, 0x24, 0xf5, 0xa1,
130 | 0xec, 0x83, 0x96, 0xd1, 0x7a, 0x0f, 0x4b, 0xe1, 0xd6, 0x38, 0xf1, 0x6f,
131 | 0xef, 0xf6, 0x08, 0x99, 0x27, 0x59, 0x88, 0x0c, 0x18, 0x02, 0x8e, 0x24,
132 | 0x5a, 0xe4, 0x74, 0x04, 0x8d, 0x79, 0x32, 0x1f, 0x5a, 0x78, 0x03, 0xf5,
133 | 0x18, 0x18, 0x02, 0xbf, 0x8f, 0xe8, 0x24, 0xe7, 0x77, 0x69, 0x38, 0x0d,
134 | 0xc4, 0xed, 0xcc, 0x4d, 0xa7, 0x61, 0xcc, 0xef, 0xe7, 0x90, 0x3a, 0xe1,
135 | 0x64, 0x7c, 0xa4, 0xeb, 0x5d, 0x4f, 0xd3, 0x04, 0xd4, 0x6b, 0xf8, 0x74,
136 | 0x62, 0xe3, 0x5f, 0x86, 0x98, 0xde, 0xe4, 0x86, 0x1b, 0x85, 0xd3, 0x78,
137 | 0xe8, 0x51, 0xf4, 0x50, 0xd8, 0x88, 0x65, 0xa1, 0xc1, 0x4b, 0xc0, 0x61,
138 | 0x83, 0xd7, 0x80, 0x8d, 0x56, 0xe6, 0xc6, 0x99, 0x01, 0xa5, 0x6a, 0x38,
139 | 0x41, 0x10, 0xc8, 0xae, 0xd2, 0x49, 0x94, 0xac, 0x86, 0x41, 0xe8, 0xfb,
140 | 0x34, 0x1e, 0xed, 0xad, 0xf7, 0xcc, 0x18, 0x34, 0x25, 0x02, 0x55, 0xc0,
141 | 0x55, 0x18, 0xa0, 0xec, 0xd7, 0x61, 0x6e, 0xe4, 0xc9, 0xc2, 0x0b, 0x0c,
142 | 0x28, 0x6e, 0x23, 0xf0, 0xab, 0xc3, 0x18, 0xa4, 0x3c, 0x22, 0xed, 0x9f,
143 | 0x49, 0x78, 0x71, 0x45, 0xae, 0xdc, 0x89, 0x9b, 0x86, 0x58, 0xcf, 0x13,
144 | 0x52, 0x0e, 0xe0, 0xd5, 0x30, 0xc3, 0x53, 0x82, 0x57, 0x41, 0x89, 0x71,
145 | 0x8d, 0x1c, 0xab, 0x07, 0xfd, 0x57, 0x12, 0x63, 0xd5, 0x09, 0x35, 0xfe,
146 | 0x9b, 0x0f, 0x6f, 0xcf, 0x8b, 0x21, 0x30, 0x68, 0x96, 0x7c, 0xad, 0x1f,
147 | 0xf2, 0x4b, 0x08, 0xa6, 0x93, 0xdc, 0x28, 0xb0, 0x00, 0x9c, 0xd5, 0xc3,
148 | 0x32, 0x2f, 0x1e, 0xd3, 0x9c, 0x9c, 0xde, 0xcc, 0xa3, 0x24, 0xa5, 0x69,
149 | 0xfb, 0xd4, 0x9f, 0x52, 0x75, 0x28, 0xfe, 0xa9, 0x1d, 0xfa, 0x0e, 0x52,
150 | 0x27, 0x28, 0xd0, 0x27, 0xe1, 0x0d, 0xf5, 0x09, 0xb0, 0x2f, 0x03, 0x21,
151 | 0xb7, 0xbc, 0x45, 0x9a, 0x02, 0xdf, 0xa3, 0xdb, 0xca, 0xf8, 0xfa, 0x3f,
152 | 0xd9, 0x62, 0x8e, 0x55, 0x23, 0x8c, 0x1e, 0xdf, 0x92, 0x13, 0xf0, 0x3d,
153 | 0x33, 0x4a, 0xdc, 0xd8, 0x27, 0xe0, 0x12, 0x53, 0x17, 0x49, 0x58, 0xef,
154 | 0xb9, 0xc8, 0x7b, 0x48, 0x72, 0x92, 0x74, 0x38, 0x8e, 0x40, 0xe0, 0x85,
155 | 0x88, 0x7d, 0xf0, 0x42, 0xa9, 0xcb, 0xf4, 0x8a, 0x11, 0x04, 0xb0, 0x4f,
156 | 0xa3, 0xc4, 0x63, 0x2d, 0x63, 0x37, 0xbd, 0xdb, 0xd0, 0x9c, 0x55, 0x10,
157 | 0xe6, 0xd4, 0x00, 0xb7, 0xec, 0x51, 0x80, 0x5f, 0xa5, 0xee, 0xbc, 0xa2,
158 | 0x4f, 0x64, 0x30, 0xbf, 0x61, 0x28, 0xc2, 0x78, 0x92, 0xd4, 0x0c, 0xdf,
159 | 0xd2, 0x8e, 0x7a, 0x8c, 0xa0, 0x33, 0x60, 0x29, 0x77, 0xea, 0x00, 0xf4,
160 | 0xf3, 0x0c, 0x33, 0x74, 0x08, 0xc4, 0x45, 0xdf, 0x22, 0x86, 0x10, 0xcd,
161 | 0x70, 0xe1, 0x4e, 0xca, 0x10, 0x82, 0xab, 0x21, 0xaa, 0x7a, 0x75, 0x4e,
162 | 0xe3, 0x56, 0x02, 0x72, 0xf4, 0x64, 0xbc, 0x00, 0x93, 0x89, 0xef, 0x2a,
163 | 0x26, 0x60, 0x70, 0x1e, 0x45, 0x68, 0x37, 0xd3, 0x94, 0xde, 0x32, 0xad,
164 | 0x8f, 0x12, 0x37, 0x1f, 0xa2, 0x51, 0x71, 0x43, 0x43, 0x03, 0x15, 0xcc,
165 | 0x02, 0xec, 0x8b, 0x3c, 0x02, 0xa7, 0x5c, 0x3c, 0x83, 0xdc, 0x32, 0x40,
166 | 0x30, 0x4f, 0x42, 0x69, 0x34, 0x92, 0x39, 0xb6, 0x35, 0xbf, 0x21, 0x1d,
167 | 0x0b, 0x19, 0x04, 0xbc, 0x4f, 0xc1, 0x6a, 0xb8, 0x39, 0x5b, 0x66, 0x37,
168 | 0xfb, 0x23, 0x70, 0x92, 0x9f, 0xa4, 0xcf, 0x41, 0x84, 0xc7, 0x00, 0x4f,
169 | 0x60, 0x20, 0x8d, 0x46, 0xea, 0xfa, 0xe1, 0x22, 0x1b, 0x6e, 0xf7, 0x32,
170 | 0x67, 0x50, 0xdf, 0xcd, 0xbd, 0x47, 0x65, 0xbc, 0xb5, 0xdd, 0x5d, 0x41,
171 | 0x60, 0x6d, 0xf0, 0x71, 0x18, 0x20, 0xa3, 0x6b, 0xb9, 0xf9, 0xd4, 0xf7,
172 | 0x7d, 0x09, 0x2d, 0x04, 0x73, 0x57, 0x48, 0xab, 0x6f, 0xfd, 0x63, 0x43,
173 | 0x4a, 0xe0, 0x5a, 0x13, 0xa8, 0xa3, 0xab, 0x4e, 0x48, 0xb2, 0xbf, 0xd6,
174 | 0x0b, 0x3e, 0xe8, 0xfc, 0x36, 0x84, 0xa6, 0xb8, 0x3c, 0xdf, 0x4d, 0xaf,
175 | 0xb9, 0xa0, 0x41, 0xb7, 0xde, 0x5c, 0xbc, 0x3d, 0x2d, 0x29, 0xb3, 0xad,
176 | 0x2d, 0xd2, 0x4a, 0xa7, 0xf6, 0xf4, 0xfd, 0xf9, 0xcb, 0xff, 0x9c, 0x9f,
177 | 0x5d, 0x7d, 0xb8, 0xab, 0xce, 0xc4, 0xa6, 0x67, 0x6b, 0x02, 0x98, 0xc5,
178 | 0x1c, 0xf7, 0xd6, 0x5a, 0xe6, 0x24, 0x89, 0x60, 0x7a, 0x7e, 0x9f, 0xf9,
179 | 0xea, 0xd3, 0x24, 0x8c, 0xa8, 0x68, 0x67, 0xdb, 0x56, 0xfc, 0x1e, 0x86,
180 | 0x41, 0x46, 0x09, 0xbd, 0xee, 0x32, 0x49, 0xe1, 0x96, 0xb7, 0xb2, 0x38,
181 | 0x28, 0xe2, 0x65, 0x95, 0xcf, 0x43, 0xa9, 0xaa, 0x1b, 0x16, 0x2e, 0x54,
182 | 0xa5, 0x03, 0x0a, 0x65, 0x15, 0xfe, 0x1b, 0x02, 0x0d, 0x1d, 0x4a, 0xfd,
183 | 0x92, 0x4a, 0x87, 0x20, 0xb6, 0x68, 0x63, 0x0c, 0x37, 0x90, 0x82, 0x4c,
184 | 0xf1, 0xfd, 0x8f, 0x34, 0x5d, 0x42, 0x30, 0x6b, 0x8b, 0xdc, 0x5b, 0xb0,
185 | 0xa1, 0x2c, 0x67, 0x58, 0x98, 0xe8, 0xf9, 0xa2, 0x71, 0xf3, 0xb1, 0x65,
186 | 0xe2, 0x6f, 0xc9, 0xe6, 0xae, 0x98, 0x36, 0xa0, 0x8c, 0x8b, 0xf2, 0x11,
187 | 0xa6, 0x83, 0xac, 0xce, 0x8d, 0x84, 0xfc, 0x67, 0x30, 0x6b, 0x44, 0x37,
188 | 0x16, 0x55, 0x55, 0x62, 0x55, 0xb3, 0x2b, 0x33, 0x72, 0xd5, 0x94, 0x6c,
189 | 0x15, 0x4f, 0x42, 0x38, 0xb2, 0xaf, 0x14, 0x4a, 0xd1, 0x22, 0x44, 0x23,
190 | 0x9f, 0x4b, 0x01, 0x15, 0x10, 0x8a, 0x78, 0x4a, 0xf5, 0xdf, 0xf6, 0x00,
191 | 0x8a, 0xa0, 0x14, 0xc7, 0x02, 0xfa, 0x91, 0x41, 0x62, 0x90, 0xe5, 0xcc,
192 | 0xd7, 0xb6, 0x9e, 0x72, 0xfc, 0x6c, 0x9f, 0x53, 0x3e, 0xa0, 0x17, 0xbd,
193 | 0xdb, 0x15, 0x8c, 0x05, 0x1f, 0x6c, 0xc1, 0x84, 0x1a, 0xf9, 0x28, 0xe2,
194 | 0x96, 0x4c, 0x41, 0x4c, 0x8a, 0x53, 0xb6, 0xef, 0x67, 0x36, 0x90, 0x18,
195 | 0xd3, 0x15, 0xa4, 0xb7, 0xc2, 0xe3, 0x7e, 0x9f, 0xf1, 0x09, 0xc1, 0xf6,
196 | 0xc4, 0x5c, 0x52, 0x3f, 0x26, 0x11, 0xbd, 0xa9, 0x26, 0x26, 0xc5, 0x74,
197 | 0x77, 0xe5, 0x28, 0x69, 0x84, 0x5c, 0x57, 0x64, 0x32, 0x00, 0xb2, 0x01,
198 | 0x5a, 0x97, 0x6e, 0xc8, 0xf6, 0xc6, 0xef, 0xcf, 0x57, 0x14, 0x7d, 0x2f,
199 | 0xd7, 0x53, 0xb8, 0xfe, 0x8d, 0x69, 0xca, 0x48, 0x26, 0x2d, 0xa4, 0x06,
200 | 0x07, 0xfa, 0x30, 0x70, 0x51, 0x73, 0x37, 0xa6, 0xd1, 0xf7, 0xb3, 0x45,
201 | 0xd1, 0x8a, 0x30, 0x0e, 0x40, 0x53, 0x59, 0x64, 0x7a, 0x3a, 0x67, 0x1b,
202 | 0xd7, 0xa8, 0x15, 0x8b, 0xec, 0xfb, 0xc3, 0x27, 0xc7, 0x22, 0x08, 0xbd,
203 | 0x53, 0xb8, 0x1e, 0xc6, 0x18, 0xae, 0xb6, 0x61, 0x88, 0x59, 0x5a, 0x4a,
204 | 0x9d, 0x16, 0x03, 0x65, 0x4b, 0x58, 0x1b, 0xb8, 0x9f, 0x28, 0xf4, 0x39,
205 | 0xc8, 0x4e, 0xa5, 0xc1, 0x9d, 0x39, 0x6e, 0x4f, 0xa5, 0xed, 0xb8, 0x08,
206 | 0x5b, 0x89, 0x12, 0xdc, 0x25, 0x03, 0xc3, 0x42, 0x9f, 0xd8, 0x60, 0xcc,
207 | 0xc2, 0x6b, 0xc1, 0x70, 0x1f, 0x12, 0xa4, 0x9c, 0x6e, 0x85, 0x95, 0x1d,
208 | 0x03, 0x2a, 0x9c, 0x4c, 0x29, 0x8b, 0x34, 0x4f, 0x71, 0x10, 0x2c, 0x4f,
209 | 0x5d, 0xb9, 0x74, 0xfe, 0x3f, 0x98, 0x47, 0x2b, 0x93, 0x79, 0xae, 0x4f,
210 | 0xf3, 0x71, 0xb4, 0xa0, 0xa5, 0xeb, 0x2d, 0x24, 0x09, 0xc2, 0x02, 0x99,
211 | 0x84, 0xf9, 0x2d, 0xc4, 0xed, 0xc3, 0xae, 0x4a, 0xd3, 0xbd, 0x3c, 0x16,
212 | 0xee, 0xfd, 0x7b, 0xc8, 0xef, 0x72, 0x7d, 0x65, 0x44, 0xdb, 0xfd, 0x7f,
213 | 0x94, 0x64, 0x8b, 0x87, 0x6a, 0xb8, 0xe0, 0xda, 0xb3, 0xc5, 0x53, 0xa6,
214 | 0x4e, 0x3b, 0x7c, 0x6a, 0x35, 0x45, 0x41, 0x72, 0xa1, 0x0c, 0xe2, 0x25,
215 | 0xcc, 0x51, 0x5b, 0xbc, 0xa4, 0xc3, 0x62, 0x85, 0x78, 0x91, 0x9b, 0x65,
216 | 0x8e, 0x26, 0xd3, 0x7e, 0x7c, 0xa5, 0x32, 0x27, 0xa1, 0xef, 0x68, 0x4a,
217 | 0x62, 0xa9, 0x1d, 0x3f, 0x8b, 0xc7, 0xd9, 0x1c, 0x2a, 0xa9, 0x39, 0x74,
218 | 0xfb, 0xe1, 0x52, 0x8e, 0x82, 0x1c, 0x01, 0x06, 0x10, 0xc2, 0xda, 0x70,
219 | 0x10, 0x4f, 0xf8, 0x78, 0x1b, 0xd7, 0x09, 0x05, 0x14, 0x5d, 0x6f, 0xa6,
220 | 0x71, 0xe4, 0xe1, 0x38, 0x75, 0xd3, 0x5b, 0x0e, 0xa2, 0x1d, 0x9f, 0x9f,
221 | 0xbd, 0xba, 0x7c, 0x79, 0xf9, 0x9f, 0xa3, 0x36, 0x6f, 0xb8, 0x6f, 0xf8,
222 | 0xf1, 0xaf, 0xa7, 0x00, 0xfb, 0xfa, 0xec, 0xe2, 0x51, 0xc0, 0xbf, 0xbc,
223 | 0xfc, 0x78, 0x71, 0x79, 0xf6, 0xe1, 0xf4, 0xaa, 0x02, 0xdd, 0x06, 0x72,
224 | 0x91, 0x0f, 0xfc, 0x22, 0x69, 0x97, 0x49, 0x82, 0xa6, 0xa0, 0x11, 0x19,
225 | 0x10, 0x5f, 0xd0, 0x5c, 0x76, 0xa8, 0xc1, 0x1d, 0x08, 0x72, 0xc3, 0xe2,
226 | 0x0d, 0x27, 0x4a, 0x1f, 0x63, 0xa9, 0x69, 0x9a, 0x9c, 0x59, 0x1b, 0x73,
227 | 0x88, 0x85, 0xfe, 0xd1, 0x29, 0x8e, 0xb2, 0xe5, 0x54, 0xf6, 0xf0, 0x37,
228 | 0x89, 0x37, 0xb3, 0x28, 0xce, 0xf8, 0x4b, 0xbb, 0xa2, 0x96, 0x35, 0x93,
229 | 0x74, 0xda, 0xee, 0x58, 0x96, 0xd5, 0x06, 0x70, 0x4d, 0x78, 0x4b, 0x47,
230 | 0xeb, 0xf4, 0x34, 0x82, 0x6f, 0x38, 0x5f, 0x25, 0x37, 0x8e, 0x66, 0x11,
231 | 0xf0, 0x96, 0x3d, 0x82, 0x6d, 0xfc, 0xbd, 0x26, 0x76, 0x1f, 0x1f, 0xcd,
232 | 0xdd, 0x3c, 0x20, 0x40, 0xdf, 0x5b, 0xe8, 0x0f, 0x3a, 0xbd, 0x65, 0xa7,
233 | 0xf7, 0xc6, 0xfa, 0xaa, 0x11, 0x74, 0xdd, 0xa8, 0x1b, 0x31, 0xd5, 0xda,
234 | 0x0a, 0x90, 0x7d, 0x48, 0x06, 0x91, 0xd1, 0x23, 0xbd, 0xa0, 0xeb, 0x59,
235 | 0xa4, 0x6b, 0x76, 0x6d, 0xa3, 0x63, 0xee, 0x1f, 0x92, 0x7d, 0x63, 0x1f,
236 | 0xfe, 0xdb, 0xa6, 0x65, 0x13, 0x0b, 0x2e, 0x87, 0x07, 0x86, 0xd9, 0xe9,
237 | 0x43, 0xd7, 0xc0, 0x30, 0x0f, 0x22, 0x68, 0xe8, 0xed, 0x13, 0xfc, 0x39,
238 | 0x19, 0x40, 0x17, 0xb1, 0x0f, 0xcd, 0x7e, 0x0f, 0xf2, 0x18, 0xb3, 0xd7,
239 | 0x05, 0xff, 0x4d, 0xec, 0x0e, 0xfc, 0x7a, 0x3d, 0xb3, 0xd7, 0x01, 0x0a,
240 | 0x07, 0x46, 0xd7, 0xec, 0x0f, 0xe0, 0x32, 0x08, 0xba, 0x30, 0x91, 0xd1,
241 | 0xfb, 0xfa, 0x16, 0x86, 0x76, 0x3c, 0xcb, 0xc0, 0xc9, 0x08, 0x4e, 0xc6,
242 | 0xa6, 0x42, 0x7c, 0x38, 0x19, 0xc1, 0xc9, 0x60, 0x2e, 0xe8, 0x19, 0xc0,
243 | 0x54, 0x38, 0x09, 0x9b, 0xee, 0xc4, 0xee, 0x9b, 0x56, 0x97, 0xf4, 0xd8,
244 | 0xcc, 0x80, 0xf2, 0x80, 0xf4, 0x70, 0xa2, 0x9e, 0x67, 0xf0, 0x89, 0x8c,
245 | 0x01, 0xc1, 0x89, 0xe0, 0x32, 0x78, 0x63, 0x47, 0xb0, 0x20, 0x02, 0x73,
246 | 0xbd, 0xd9, 0xff, 0x8a, 0xab, 0x45, 0x26, 0x1e, 0xcb, 0x5d, 0x9f, 0x3c,
247 | 0x21, 0x42, 0x65, 0x77, 0x49, 0x56, 0xaa, 0x65, 0xbd, 0x68, 0x37, 0xa1,
248 | 0x0b, 0xbd, 0xfc, 0x11, 0x65, 0x93, 0x29, 0x4d, 0xb6, 0x8b, 0x26, 0xe1,
249 | 0x99, 0x38, 0xc2, 0xa0, 0xcb, 0xda, 0x94, 0x34, 0x06, 0x5f, 0xe5, 0x06,
250 | 0x5d, 0x31, 0x1b, 0xeb, 0x2b, 0xb3, 0x1a, 0xec, 0x53, 0x30, 0xf2, 0xfe,
251 | 0x22, 0xbf, 0x80, 0xde, 0x30, 0x9e, 0x2f, 0x80, 0x2f, 0xb7, 0x73, 0x0a,
252 | 0xb4, 0x43, 0x3c, 0xd7, 0x14, 0x08, 0x0d, 0xed, 0xc0, 0xa3, 0x01, 0x0b,
253 | 0x2b, 0x8e, 0x76, 0x8a, 0x1e, 0x93, 0xb8, 0xe4, 0xdf, 0x97, 0xe7, 0xc5,
254 | 0x40, 0x05, 0x1d, 0xf7, 0x01, 0x02, 0x55, 0xb6, 0x18, 0xcf, 0x42, 0x40,
255 | 0xb6, 0x74, 0xc1, 0x55, 0x83, 0x4d, 0x02, 0xf9, 0x62, 0x5c, 0xe1, 0x7b,
256 | 0x70, 0xa8, 0x1a, 0xd5, 0x35, 0x65, 0x05, 0x6a, 0x40, 0xd6, 0xb6, 0x5f,
257 | 0x4c, 0xcf, 0xab, 0xac, 0xad, 0x04, 0xd5, 0x3a, 0x2b, 0x63, 0x28, 0x53,
258 | 0xba, 0x0c, 0x93, 0x45, 0x26, 0xe9, 0x7c, 0x84, 0xe5, 0x95, 0x96, 0xf5,
259 | 0x90, 0x11, 0x2a, 0x46, 0x05, 0xfa, 0x1c, 0x74, 0x96, 0x76, 0x07, 0xd4,
260 | 0x6f, 0x06, 0x4a, 0x49, 0xf6, 0xa3, 0x01, 0xfe, 0x7e, 0xe4, 0xda, 0xf8,
261 | 0x28, 0x03, 0x65, 0x2a, 0xfb, 0xc0, 0x2a, 0x0b, 0x36, 0xcd, 0xdd, 0x45,
262 | 0x56, 0xeb, 0x5a, 0x18, 0x00, 0x76, 0xfe, 0xf5, 0x2b, 0xb6, 0x0f, 0x83,
263 | 0xde, 0xc7, 0xfe, 0x9b, 0xfd, 0xa5, 0xdd, 0xfb, 0x3a, 0x1b, 0x18, 0x76,
264 | 0x0f, 0x6e, 0xb0, 0x25, 0x00, 0x8b, 0xff, 0x53, 0x57, 0xbd, 0x63, 0x99,
265 | 0x31, 0x28, 0xee, 0xdf, 0xb0, 0xca, 0x01, 0x8a, 0xd2, 0xd8, 0x3f, 0x07,
266 | 0x11, 0x83, 0x80, 0xbf, 0xbe, 0xb5, 0xf9, 0x4d, 0xd0, 0xf9, 0xb8, 0x1f,
267 | 0x18, 0x9d, 0xbf, 0x65, 0xa5, 0x6c, 0x8f, 0xe7, 0xf1, 0x2b, 0xfd, 0xd1,
268 | 0xd8, 0xf1, 0xf1, 0xbe, 0xe8, 0x61, 0x93, 0x03, 0xd0, 0xf4, 0x0e, 0x2e,
269 | 0x7d, 0x66, 0x41, 0x10, 0xe9, 0x2c, 0x19, 0x1b, 0x66, 0xb6, 0x71, 0x78,
270 | 0xb2, 0x6f, 0xf6, 0x06, 0x04, 0x42, 0x01, 0x11, 0x37, 0x76, 0x27, 0xeb,
271 | 0xe1, 0x9d, 0x6d, 0x15, 0xff, 0x0c, 0xd1, 0x60, 0xd8, 0xd6, 0x95, 0x7d,
272 | 0x60, 0xf6, 0x3b, 0x0c, 0x8c, 0x30, 0x64, 0xf6, 0x80, 0xb9, 0x76, 0x8c,
273 | 0x3f, 0x2c, 0x86, 0x1c, 0xc2, 0x65, 0x90, 0xf1, 0x1b, 0xc2, 0xfc, 0x3c,
274 | 0x3a, 0x7c, 0x08, 0x5f, 0x3c, 0xc4, 0x1c, 0xf2, 0xb6, 0xd2, 0xdf, 0x23,
275 | 0x63, 0x9f, 0x18, 0xc6, 0x1f, 0x64, 0x2e, 0x4f, 0xb6, 0xff, 0x46, 0xf6,
276 | 0xee, 0x66, 0x6e, 0x97, 0x1c, 0x02, 0x37, 0x7b, 0x51, 0x9f, 0xf4, 0x3f,
277 | 0xf6, 0xce, 0x0f, 0xc8, 0xe1, 0x9b, 0x2e, 0x30, 0x16, 0xfd, 0x09, 0x84,
278 | 0x6a, 0x88, 0x89, 0x07, 0x07, 0x18, 0x9f, 0x3b, 0xb0, 0xf8, 0xce, 0x21,
279 | 0x44, 0xe6, 0x3e, 0x70, 0xcb, 0xea, 0x2e, 0x07, 0xa6, 0xd5, 0xf7, 0x20,
280 | 0x60, 0x62, 0xa0, 0x86, 0x68, 0x6c, 0x62, 0xd0, 0x66, 0xd1, 0x94, 0xf5,
281 | 0xa3, 0xc6, 0xf6, 0x80, 0x6f, 0x9d, 0xee, 0xb2, 0x63, 0x5a, 0xfb, 0x1e,
282 | 0x04, 0xd9, 0x43, 0x73, 0xb0, 0x4f, 0xfa, 0xc8, 0xcb, 0x1e, 0x5c, 0xf6,
283 | 0xcd, 0x03, 0x3b, 0x83, 0x21, 0x20, 0xd9, 0xbe, 0x39, 0xe8, 0x1b, 0xbc,
284 | 0x85, 0x03, 0xc3, 0x78, 0xdb, 0x30, 0x0f, 0x41, 0xe6, 0x28, 0x98, 0x43,
285 | 0xb8, 0x40, 0x84, 0x3e, 0x40, 0xe8, 0xc3, 0x43, 0xe3, 0x00, 0xd0, 0x18,
286 | 0xbc, 0xe5, 0x6b, 0x45, 0xc1, 0x71, 0xcb, 0x1d, 0x44, 0xc1, 0x63, 0x45,
287 | 0xbd, 0x1f, 0xe3, 0xf1, 0x22, 0xc5, 0x23, 0x62, 0x1a, 0x81, 0xea, 0x1b,
288 | 0x18, 0x08, 0x57, 0x17, 0x18, 0xd9, 0xb1, 0x55, 0xb1, 0xf0, 0x1a, 0xa8,
289 | 0x88, 0x28, 0x96, 0xf6, 0x1d, 0xd6, 0xc3, 0xde, 0xf4, 0xfe, 0xd5, 0x8e,
290 | 0xc2, 0xee, 0x13, 0x1b, 0x44, 0x87, 0x86, 0x01, 0xde, 0x10, 0x14, 0x38,
291 | 0x38, 0xc0, 0xfb, 0x03, 0x6e, 0x24, 0xfb, 0x78, 0xbf, 0xff, 0x15, 0x04,
292 | 0x0c, 0x3a, 0x8e, 0x09, 0x0f, 0xa4, 0x3c, 0xcc, 0x50, 0x3a, 0xc1, 0x3e,
293 | 0x88, 0x0e, 0x93, 0xa0, 0x8e, 0x81, 0x4d, 0x46, 0xe7, 0xe3, 0xe0, 0x4d,
294 | 0x77, 0x69, 0x5b, 0x4c, 0x6a, 0xe0, 0x4a, 0x21, 0x83, 0xb2, 0x0d, 0xfb,
295 | 0xcd, 0xfe, 0x39, 0xe8, 0xc4, 0x1b, 0xb4, 0x3b, 0x5b, 0x71, 0x39, 0x8a,
296 | 0x1a, 0x6d, 0x2b, 0x59, 0xc5, 0xe5, 0x6c, 0x26, 0x17, 0xd5, 0xea, 0x47,
297 | 0x3b, 0x16, 0xcf, 0x44, 0x34, 0x48, 0x70, 0xf9, 0xee, 0x85, 0x1f, 0xf7,
298 | 0x43, 0xa6, 0xfd, 0x16, 0x24, 0x70, 0xe7, 0x10, 0xc9, 0x41, 0x8d, 0x3c,
299 | 0x27, 0xcd, 0x55, 0x18, 0xfb, 0xc9, 0xca, 0x94, 0x45, 0x87, 0x89, 0x30,
300 | 0x78, 0xf6, 0x8b, 0xbc, 0x20, 0x3b, 0xbb, 0x86, 0xf2, 0xf4, 0xa0, 0x3e,
301 | 0x12, 0xe8, 0x57, 0x59, 0x81, 0x7c, 0x95, 0xfd, 0x39, 0xa8, 0x01, 0x83,
302 | 0xd6, 0x5e, 0x65, 0x9a, 0x9c, 0x82, 0xa5, 0x63, 0xec, 0x5c, 0x9e, 0x43,
303 | 0x1a, 0xff, 0x97, 0xbc, 0xfc, 0x01, 0xdf, 0x6a, 0xf7, 0x4b, 0xe7, 0x0a,
304 | 0x3a, 0x24, 0x9d, 0x2b, 0x9a, 0x5b, 0x1f, 0xdd, 0xeb, 0xc1, 0xb6, 0x7b,
305 | 0x3d, 0x40, 0xc7, 0x7a, 0x08, 0x10, 0x70, 0x3d, 0x11, 0x2e, 0x95, 0xfd,
306 | 0x2b, 0xee, 0xed, 0x4e, 0xe9, 0x6a, 0x6d, 0x04, 0x44, 0x9f, 0xc0, 0x32,
307 | 0xf6, 0x22, 0x93, 0x36, 0x78, 0x26, 0xcd, 0xdd, 0xed, 0x40, 0x75, 0xb7,
308 | 0x03, 0xe9, 0x6e, 0x07, 0x9b, 0xee, 0xb6, 0x21, 0xb9, 0xca, 0x8f, 0xfa,
309 | 0x70, 0x0b, 0x73, 0x1a, 0x47, 0xe1, 0x6c, 0xca, 0x0f, 0x25, 0xb6, 0xf9,
310 | 0xf6, 0x02, 0xf2, 0xd6, 0x64, 0xcc, 0x53, 0x98, 0x4d, 0xd4, 0xad, 0x07,
311 | 0x40, 0x09, 0xc8, 0xf6, 0xf0, 0x15, 0xb9, 0x48, 0xd4, 0x21, 0x6b, 0x44,
312 | 0x3d, 0x00, 0xc1, 0xb1, 0xf7, 0xe6, 0xfc, 0x15, 0x0d, 0x32, 0xca, 0x69,
313 | 0xb4, 0x1b, 0xbc, 0x91, 0x6f, 0x5e, 0xbf, 0x4f, 0x32, 0xe7, 0xd3, 0xe7,
314 | 0x11, 0x69, 0xb7, 0xdd, 0x34, 0x85, 0x74, 0x13, 0x72, 0xfd, 0x6b, 0x4a,
315 | 0xe7, 0xb8, 0x9d, 0xef, 0x5d, 0xe3, 0xa9, 0xd2, 0x98, 0x66, 0xf8, 0xbe,
316 | 0x86, 0xef, 0x92, 0x64, 0x7b, 0x7b, 0xc5, 0x61, 0xbd, 0xc5, 0xdc, 0x77,
317 | 0x73, 0x7a, 0x2e, 0xb4, 0xe7, 0x95, 0x9b, 0x36, 0x75, 0xdc, 0x05, 0xf8,
318 | 0xa9, 0xd9, 0x50, 0x5f, 0xcb, 0x34, 0x74, 0x13, 0xdf, 0x74, 0x35, 0xb5,
319 | 0x73, 0x4e, 0x18, 0x41, 0x22, 0x86, 0x04, 0x75, 0x53, 0xa1, 0x4a, 0xc7,
320 | 0x1a, 0x7d, 0x0b, 0x35, 0x1f, 0xf1, 0x0b, 0x9b, 0xb9, 0xc9, 0x09, 0x68,
321 | 0xc1, 0xaf, 0xb8, 0x4b, 0x81, 0xb0, 0x24, 0xa5, 0x7c, 0x19, 0x7c, 0x6a,
322 | 0x73, 0x4a, 0xf3, 0xa6, 0xc2, 0x01, 0xd0, 0xde, 0x17, 0x13, 0x91, 0x87,
323 | 0xc3, 0x03, 0x8d, 0xf1, 0x6c, 0xc8, 0xbf, 0x2f, 0xcf, 0x4e, 0x92, 0xd9,
324 | 0x1c, 0x94, 0x27, 0xce, 0x05, 0x56, 0x5d, 0x87, 0xd1, 0xa6, 0x0f, 0x4d,
325 | 0xe5, 0xb9, 0x09, 0x76, 0xea, 0x80, 0x20, 0x5a, 0xbe, 0x26, 0x51, 0xbc,
326 | 0xca, 0xf5, 0xb0, 0x6e, 0x93, 0x4f, 0xfe, 0x21, 0x99, 0x37, 0x2d, 0x7d,
327 | 0xc4, 0x40, 0xc3, 0x49, 0xb3, 0x4a, 0x58, 0x75, 0x6c, 0x39, 0xa0, 0x60,
328 | 0xbf, 0x39, 0x87, 0x27, 0x5d, 0x0c, 0xaf, 0x42, 0x7b, 0x59, 0xd6, 0xbc,
329 | 0xd3, 0xc4, 0xf6, 0x8c, 0x36, 0xb4, 0xd7, 0x02, 0xaa, 0x86, 0xf5, 0xd8,
330 | 0xb1, 0x66, 0xcb, 0x98, 0xb8, 0x61, 0xa4, 0x1e, 0xff, 0xd8, 0xb5, 0x82,
331 | 0xc6, 0xae, 0x2a, 0xab, 0x81, 0xac, 0x2a, 0x0c, 0xfd, 0x39, 0x69, 0x9c,
332 | 0x5e, 0x5e, 0x5e, 0x5c, 0x0e, 0xc9, 0x49, 0xb2, 0x88, 0x7c, 0x12, 0x27,
333 | 0x39, 0xc1, 0x37, 0xee, 0x44, 0x43, 0xb8, 0xef, 0x61, 0x36, 0xa2, 0xd4,
334 | 0xd0, 0xab, 0x36, 0xc4, 0x72, 0x54, 0xf5, 0x2c, 0xe4, 0xfb, 0x78, 0x7e,
335 | 0xac, 0x37, 0xb4, 0x07, 0x94, 0xe0, 0x3d, 0xb0, 0x9f, 0xe6, 0x59, 0x53,
336 | 0x51, 0x8b, 0xd2, 0xf9, 0xa2, 0x4f, 0x13, 0x45, 0x60, 0xa6, 0xed, 0x92,
337 | 0x3c, 0x67, 0x1b, 0xb3, 0x13, 0x01, 0x0a, 0xf6, 0x84, 0x3d, 0x66, 0x86,
338 | 0x9b, 0xf3, 0xcd, 0xc6, 0x7f, 0xe3, 0x86, 0x22, 0x33, 0x59, 0x00, 0x17,
339 | 0xcc, 0x95, 0x9d, 0x72, 0xb4, 0x09, 0x35, 0xeb, 0xa9, 0xeb, 0x05, 0xe5,
340 | 0x34, 0xb8, 0xc3, 0x2f, 0xa6, 0x61, 0x6a, 0xc3, 0x9e, 0x37, 0x70, 0xb9,
341 | 0xf3, 0x39, 0x1e, 0xd4, 0x50, 0x44, 0x55, 0xee, 0xed, 0x63, 0x35, 0x59,
342 | 0x78, 0x0a, 0xdc, 0xfd, 0x4a, 0x6a, 0x1d, 0x45, 0x05, 0xac, 0xd8, 0xdd,
343 | 0xdc, 0xf6, 0x27, 0x45, 0x17, 0xa6, 0x01, 0x50, 0x5a, 0x16, 0xa5, 0x39,
344 | 0x96, 0xb7, 0x4c, 0x27, 0x90, 0x40, 0x94, 0x1d, 0x78, 0x2f, 0xe8, 0x3f,
345 | 0x56, 0x25, 0xc8, 0x34, 0xef, 0x01, 0xf5, 0xdb, 0x62, 0xd1, 0x0f, 0xea,
346 | 0x9f, 0xe0, 0x2c, 0xc1, 0x1d, 0xa7, 0x92, 0x16, 0xa1, 0x0c, 0x28, 0x38,
347 | 0x11, 0x8b, 0xf1, 0x10, 0x1f, 0x68, 0x53, 0xa1, 0x1e, 0xa0, 0x18, 0x17,
348 | 0xbc, 0x07, 0xcf, 0x4a, 0x5d, 0x2c, 0xf2, 0x26, 0x37, 0xf3, 0xca, 0x81,
349 | 0x36, 0x75, 0xac, 0x2e, 0x37, 0xb4, 0x65, 0x03, 0x68, 0x42, 0xfd, 0x49,
350 | 0x2b, 0x5c, 0xa6, 0x00, 0x14, 0xda, 0xda, 0x10, 0x9b, 0xa2, 0x8d, 0x16,
351 | 0x69, 0x8c, 0xc1, 0x2d, 0x5e, 0x03, 0x95, 0xeb, 0x16, 0xe9, 0x42, 0x3c,
352 | 0xb4, 0x14, 0x4a, 0x63, 0x34, 0x12, 0xf4, 0xe7, 0x80, 0xba, 0xc4, 0x47,
353 | 0xa4, 0x3f, 0xad, 0x66, 0x1b, 0x8a, 0x8e, 0x99, 0x2c, 0x5c, 0x36, 0x1b,
354 | 0x16, 0x2e, 0x7e, 0x3d, 0x52, 0x8c, 0x00, 0x77, 0x3a, 0xde, 0x8a, 0x13,
355 | 0xad, 0x62, 0xb8, 0xbe, 0x13, 0x1d, 0xf8, 0x2b, 0x70, 0x41, 0x1c, 0xab,
356 | 0x04, 0x96, 0xa8, 0xc5, 0x42, 0x0a, 0xab, 0x6b, 0x69, 0xb6, 0x06, 0x23,
357 | 0x00, 0x3d, 0x8c, 0x70, 0xf1, 0x3b, 0x81, 0x9c, 0x36, 0x99, 0x90, 0x15,
358 | 0xcb, 0x84, 0x44, 0x73, 0xdd, 0xe2, 0x8d, 0xbf, 0x2f, 0xe8, 0x82, 0x0e,
359 | 0x27, 0x6e, 0x94, 0xd1, 0x96, 0x50, 0x76, 0x7f, 0x21, 0xde, 0xe3, 0xdb,
360 | 0xb6, 0x65, 0xc9, 0x46, 0x4f, 0x70, 0x60, 0xa8, 0x70, 0x83, 0xeb, 0xd6,
361 | 0x9e, 0x5e, 0x59, 0x99, 0x38, 0x24, 0xf4, 0x12, 0xdc, 0xa8, 0x3b, 0xce,
362 | 0x24, 0x97, 0xea, 0x6c, 0x9c, 0x25, 0x5f, 0xb8, 0x23, 0x73, 0xbf, 0x91,
363 | 0x57, 0x42, 0x26, 0xb6, 0x0b, 0xaf, 0x24, 0x8e, 0x56, 0x45, 0xc9, 0xb4,
364 | 0xd9, 0x10, 0xdf, 0x0a, 0x00, 0xb2, 0x21, 0x41, 0xfd, 0xe4, 0x67, 0xd2,
365 | 0xb8, 0x85, 0x97, 0x8e, 0x7c, 0x91, 0x05, 0xcd, 0x5d, 0xfe, 0x5e, 0xaf,
366 | 0xfa, 0xee, 0x4a, 0x6c, 0x53, 0x1c, 0x20, 0xe8, 0x09, 0xe8, 0x4a, 0x9e,
367 | 0x2e, 0x28, 0xc0, 0xef, 0x30, 0x2a, 0x85, 0x76, 0x95, 0x48, 0xcd, 0x2b,
368 | 0x0c, 0x04, 0x98, 0x41, 0x4a, 0x92, 0x89, 0x41, 0x84, 0x54, 0x79, 0xe0,
369 | 0x2d, 0x88, 0xe7, 0xc8, 0xdd, 0x68, 0xe5, 0xde, 0x66, 0x5b, 0x36, 0x5b,
370 | 0x63, 0x27, 0x7c, 0x05, 0xaa, 0x7f, 0x15, 0x58, 0x40, 0x8f, 0x7f, 0x6a,
371 | 0xca, 0x53, 0xe3, 0x3a, 0x3f, 0xc1, 0xb9, 0x81, 0x90, 0xa7, 0x3a, 0xb8,
372 | 0x2d, 0xbf, 0x33, 0x7d, 0xc4, 0xc4, 0x1a, 0x5f, 0xc8, 0x88, 0x9d, 0xbd,
373 | 0xff, 0xf7, 0x3e, 0x2f, 0xee, 0xcf, 0x8a, 0x92, 0x61, 0xc9, 0x13, 0xca,
374 | 0x9e, 0x78, 0xfc, 0xb8, 0xff, 0x75, 0xd6, 0x23, 0x83, 0xa5, 0xd1, 0x83,
375 | 0xec, 0x72, 0x09, 0x3f, 0x58, 0xb7, 0xf4, 0x96, 0x58, 0xcc, 0x43, 0x53,
376 | 0x0f, 0xa0, 0xb1, 0x8a, 0x79, 0xdb, 0xc1, 0x92, 0x06, 0xa0, 0x3a, 0x38,
377 | 0xac, 0x92, 0xf7, 0xf1, 0xc8, 0xb2, 0xc2, 0x98, 0x82, 0x07, 0xb1, 0x6b,
378 | 0x8f, 0xed, 0x35, 0x45, 0x3e, 0xdf, 0x22, 0xec, 0x60, 0x2d, 0x11, 0xdf,
379 | 0x2f, 0x10, 0x6e, 0x4a, 0x64, 0xfb, 0x9b, 0x05, 0xd2, 0xed, 0x5b, 0xd6,
380 | 0x9a, 0xab, 0xcd, 0x2a, 0x2b, 0x4f, 0xac, 0xa9, 0x3e, 0x85, 0x2a, 0x81,
381 | 0x0d, 0x2c, 0x6e, 0x86, 0x87, 0x6f, 0x1c, 0x42, 0x4d, 0x25, 0xb2, 0x69,
382 | 0xff, 0xc5, 0x03, 0xc2, 0x5c, 0x05, 0x56, 0x61, 0x0e, 0x21, 0x4b, 0x00,
383 | 0x7e, 0xb2, 0x3e, 0x17, 0xf1, 0xca, 0x73, 0x33, 0x4a, 0x34, 0xa1, 0xb7,
384 | 0xef, 0xcf, 0x31, 0x34, 0x68, 0xc3, 0x3d, 0x79, 0xcc, 0x07, 0x6c, 0x40,
385 | 0xbc, 0xc2, 0x96, 0xde, 0x63, 0xf3, 0xa4, 0x05, 0xb8, 0x11, 0x39, 0x89,
386 | 0x08, 0x7f, 0x86, 0x4d, 0x1c, 0x47, 0xd2, 0xf4, 0xc9, 0x2e, 0xa7, 0x92,
387 | 0xf1, 0x43, 0xdd, 0xdd, 0x6c, 0x11, 0xf5, 0x8d, 0x74, 0x91, 0x63, 0xbe,
388 | 0x4b, 0xf2, 0x00, 0xf7, 0x87, 0x11, 0x94, 0x9f, 0x0a, 0x2e, 0x70, 0xac,
389 | 0xd9, 0xe1, 0xe2, 0x2a, 0xca, 0x92, 0x46, 0xfa, 0x7b, 0x53, 0x99, 0x79,
390 | 0x27, 0xc9, 0xc5, 0x6b, 0xb4, 0x0a, 0x66, 0x71, 0x37, 0x06, 0xad, 0xbf,
391 | 0x1e, 0x55, 0xf8, 0x23, 0x5f, 0x88, 0x54, 0x59, 0x53, 0x1c, 0xc8, 0xd8,
392 | 0x4a, 0x16, 0xb6, 0x2c, 0xfa, 0x0c, 0x8f, 0x36, 0xe0, 0x37, 0x23, 0x12,
393 | 0xd3, 0x90, 0x15, 0x75, 0x82, 0x56, 0x33, 0xa2, 0xf1, 0x14, 0xb4, 0xd5,
394 | 0x20, 0x1d, 0x9d, 0xb4, 0xe1, 0xa7, 0xc4, 0x82, 0xf2, 0xc5, 0x3d, 0x02,
395 | 0x61, 0x6c, 0x58, 0x08, 0xc8, 0x2e, 0xdc, 0x3c, 0x67, 0x1f, 0xa3, 0x84,
396 | 0xd0, 0x61, 0x8f, 0xe0, 0x72, 0x44, 0xb6, 0x10, 0xb2, 0x76, 0x07, 0xfe,
397 | 0x3f, 0x07, 0xb4, 0x2a, 0xdb, 0xb8, 0x4a, 0x14, 0x24, 0x7c, 0x42, 0x88,
398 | 0x0d, 0x61, 0xc9, 0xd5, 0xff, 0x72, 0x76, 0x7e, 0xaa, 0x91, 0x61, 0xa5,
399 | 0x87, 0xa8, 0x54, 0xa9, 0xa5, 0x8e, 0x70, 0xc2, 0x3b, 0x52, 0x98, 0xc6,
400 | 0x68, 0x03, 0x4b, 0x85, 0xd9, 0xea, 0xa4, 0x57, 0x1f, 0x2e, 0x4f, 0x5f,
401 | 0xbe, 0x7d, 0xf4, 0xb4, 0xe2, 0xf4, 0xc2, 0x8f, 0xce, 0x8a, 0x6f, 0x34,
402 | 0x1e, 0x3d, 0xa7, 0xf4, 0x6f, 0x3f, 0x3c, 0xeb, 0xfb, 0xcb, 0xd3, 0xab,
403 | 0xd3, 0x0f, 0x8f, 0x9e, 0xf7, 0xbe, 0x1c, 0xf1, 0x31, 0x93, 0xfa, 0x74,
404 | 0xe2, 0x2e, 0xa2, 0xfc, 0xbe, 0xe9, 0x54, 0x3d, 0xdb, 0x81, 0x67, 0xbd,
405 | 0x61, 0xd6, 0x8a, 0x31, 0x6c, 0x67, 0xbb, 0xdc, 0x40, 0x79, 0x4a, 0xa8,
406 | 0x56, 0xc9, 0xf0, 0x58, 0xcc, 0x8a, 0x19, 0x69, 0x7d, 0xc2, 0x5a, 0xe8,
407 | 0xe8, 0xe7, 0x1d, 0x69, 0xab, 0x4a, 0x4f, 0x8d, 0xf9, 0xca, 0x8c, 0xb4,
408 | 0x5c, 0xb0, 0x9a, 0x55, 0x29, 0x0e, 0x63, 0x54, 0xc5, 0x51, 0x41, 0xc2,
409 | 0xf7, 0xe1, 0x14, 0x1c, 0xe1, 0x84, 0xa8, 0x63, 0x91, 0x07, 0xea, 0x5e,
410 | 0x1d, 0xf0, 0x01, 0x9c, 0xf9, 0x63, 0xb1, 0xab, 0x2f, 0xb4, 0xaa, 0x4e,
411 | 0xe6, 0x61, 0x77, 0x59, 0x33, 0x45, 0x95, 0xba, 0xce, 0x67, 0x74, 0xc9,
412 | 0x85, 0x49, 0x7d, 0xfb, 0x46, 0x36, 0x7a, 0x98, 0xda, 0xeb, 0x6c, 0xba,
413 | 0x2a, 0xfe, 0x79, 0x4a, 0x99, 0x28, 0x45, 0xb8, 0xbf, 0x7f, 0x09, 0xca,
414 | 0x7b, 0xb7, 0xea, 0x12, 0x94, 0x9e, 0x7b, 0x68, 0xae, 0xc3, 0x39, 0x4f,
415 | 0x93, 0x29, 0xa4, 0x28, 0x99, 0x82, 0x50, 0xf5, 0xab, 0xb5, 0x2b, 0x47,
416 | 0x97, 0x18, 0x27, 0x2b, 0x11, 0x92, 0x5f, 0x63, 0x5a, 0xab, 0xf4, 0x42,
417 | 0x8f, 0x09, 0x29, 0xcf, 0x15, 0x46, 0x5d, 0x3f, 0xab, 0xc7, 0xa0, 0x4e,
418 | 0x81, 0xf0, 0x79, 0x72, 0x76, 0x75, 0x71, 0x95, 0xa7, 0xf8, 0x3d, 0x1a,
419 | 0xa4, 0x82, 0x8b, 0x31, 0x2c, 0xa8, 0x69, 0xdb, 0x2d, 0x32, 0xd0, 0x37,
420 | 0xa9, 0x6f, 0xff, 0x5c, 0xa1, 0x7f, 0x1c, 0xe6, 0x90, 0x22, 0x57, 0xb5,
421 | 0x66, 0x53, 0x69, 0x04, 0x4c, 0x11, 0xfc, 0x5e, 0xf1, 0x67, 0x48, 0xf1,
422 | 0x9e, 0x97, 0x90, 0xcf, 0xb5, 0xeb, 0xf1, 0x3c, 0xd3, 0x1e, 0x21, 0x00,
423 | 0x7c, 0x47, 0x52, 0xcb, 0x7f, 0xec, 0x78, 0x98, 0xfd, 0xc5, 0xf1, 0x61,
424 | 0x8e, 0xb3, 0x78, 0x3b, 0xab, 0xa0, 0xe4, 0x39, 0x07, 0x7b, 0xcb, 0x0b,
425 | 0x4c, 0xfe, 0xf4, 0xb9, 0x12, 0x92, 0x9a, 0x0f, 0x45, 0xa3, 0xe7, 0xcf,
426 | 0x75, 0xc5, 0x73, 0x08, 0x3c, 0x3c, 0xe5, 0x56, 0x1c, 0x86, 0x7a, 0xd0,
427 | 0xad, 0xd6, 0x6d, 0x34, 0xfe, 0xac, 0x6a, 0xb8, 0x74, 0x2e, 0x42, 0xb2,
428 | 0x28, 0x65, 0x5b, 0xbf, 0xc7, 0xd3, 0x48, 0x9a, 0xb3, 0x24, 0xcd, 0x55,
429 | 0xd5, 0x42, 0x56, 0x17, 0xaf, 0xc4, 0x4b, 0x4e, 0x33, 0xe0, 0x2a, 0x9b,
430 | 0x65, 0xfd, 0xc4, 0x1d, 0xf1, 0x50, 0x71, 0x59, 0xe8, 0xc5, 0xd6, 0x65,
431 | 0xe6, 0xc7, 0x0a, 0x64, 0x35, 0xed, 0xc3, 0xef, 0x52, 0x6a, 0x4a, 0x86,
432 | 0xc6, 0xa7, 0x22, 0xdd, 0xfc, 0x0c, 0xf5, 0x35, 0xcb, 0x27, 0xa9, 0xaf,
433 | 0x6e, 0x6c, 0xd4, 0xd8, 0x60, 0x91, 0xae, 0x6c, 0x56, 0x62, 0xac, 0x18,
434 | 0x28, 0x68, 0x60, 0x9f, 0x5d, 0x7c, 0x2f, 0x11, 0x58, 0xe8, 0xf9, 0x21,
435 | 0xf5, 0x5b, 0x84, 0x7d, 0xd5, 0xc5, 0xb6, 0x04, 0xd8, 0x77, 0x8d, 0xf8,
436 | 0x88, 0x3c, 0x26, 0xe2, 0xfb, 0xae, 0xb2, 0x87, 0x37, 0x14, 0xdb, 0x70,
437 | 0xa4, 0xd9, 0x58, 0x86, 0x59, 0x38, 0x06, 0xba, 0xc1, 0x57, 0x41, 0x61,
438 | 0x27, 0xbf, 0x69, 0x65, 0xad, 0x21, 0x04, 0x16, 0xfe, 0x8d, 0x99, 0xdc,
439 | 0xe3, 0xdb, 0x91, 0x9a, 0xed, 0xdc, 0xa4, 0xd8, 0xd8, 0xa3, 0x78, 0x97,
440 | 0x90, 0x5f, 0xaf, 0x48, 0xf9, 0x89, 0xa1, 0x59, 0x49, 0xea, 0xc5, 0x19,
441 | 0x85, 0x52, 0x1b, 0x56, 0xe2, 0xdb, 0x1f, 0x5d, 0x11, 0x5f, 0xc1, 0x39,
442 | 0x86, 0xb8, 0xc2, 0x39, 0x6c, 0x78, 0x88, 0x73, 0xa7, 0x08, 0xc4, 0xab,
443 | 0x53, 0x06, 0x6f, 0xca, 0x4a, 0x9e, 0x4f, 0xb1, 0x2a, 0x3e, 0xaf, 0x51,
444 | 0x04, 0x85, 0x49, 0xb0, 0x3c, 0x2f, 0x04, 0x4b, 0x86, 0xa9, 0x1a, 0x5e,
445 | 0x14, 0x7a, 0xd7, 0x50, 0x82, 0xd6, 0x6c, 0xe1, 0xa8, 0xa0, 0x3b, 0x52,
446 | 0x64, 0x52, 0xa4, 0xf5, 0x3f, 0xb1, 0x0f, 0xbf, 0xef, 0x01, 0x94, 0xe7,
447 | 0x99, 0x35, 0x5d, 0xad, 0x35, 0xe4, 0x30, 0x34, 0x34, 0xa8, 0x99, 0x2b,
448 | 0xf5, 0x46, 0x43, 0xd6, 0xd5, 0x55, 0x37, 0x25, 0x37, 0x96, 0x20, 0xca,
449 | 0x55, 0xcc, 0x08, 0xa2, 0xe9, 0xa6, 0x99, 0x29, 0x85, 0x39, 0xdb, 0xc0,
450 | 0xd8, 0x70, 0x62, 0xea, 0x5c, 0xc5, 0x76, 0xd5, 0xb0, 0x0e, 0xc3, 0x23,
451 | 0xe6, 0x52, 0xf6, 0xbb, 0x1e, 0x9c, 0xac, 0xc4, 0xb5, 0x6b, 0x36, 0x75,
452 | 0xf7, 0xac, 0x66, 0x32, 0x95, 0x98, 0x7b, 0x66, 0x93, 0x9e, 0xa3, 0x54,
453 | 0x3a, 0x5e, 0x2e, 0x02, 0x0a, 0x3c, 0x0f, 0x57, 0x55, 0x81, 0x86, 0x38,
454 | 0x78, 0x5c, 0xaf, 0x0c, 0xbb, 0xb7, 0x65, 0x2d, 0xb3, 0x2f, 0x37, 0xaa,
455 | 0x79, 0xd1, 0x9f, 0x44, 0x3e, 0xab, 0xaa, 0x1d, 0x75, 0xa7, 0xb7, 0xd8,
456 | 0x2e, 0x57, 0xda, 0xcc, 0x10, 0xbc, 0xc6, 0xc2, 0xa7, 0x59, 0xb3, 0xd1,
457 | 0x6e, 0xe8, 0xba, 0xd2, 0x53, 0x1d, 0x2b, 0xf7, 0x60, 0xdb, 0xb8, 0x5a,
458 | 0x20, 0x96, 0x36, 0xad, 0x96, 0x61, 0xeb, 0xe6, 0x97, 0x24, 0x8c, 0x59,
459 | 0x6b, 0x81, 0xfc, 0x89, 0xfa, 0x92, 0x81, 0x54, 0x11, 0xb2, 0x37, 0x21,
460 | 0x08, 0xf7, 0xd0, 0x96, 0x8c, 0xa0, 0xbf, 0x25, 0x76, 0x65, 0x1e, 0xe0,
461 | 0x9a, 0x72, 0x40, 0xfb, 0xaf, 0xe5, 0x1c, 0xc1, 0xa5, 0xfe, 0xaf, 0x96,
462 | 0x63, 0xdb, 0x6d, 0x1a, 0x86, 0xfe, 0x8a, 0x9b, 0x4d, 0x4a, 0x42, 0x4b,
463 | 0x0b, 0x42, 0xbc, 0x6c, 0xb0, 0x09, 0x81, 0x90, 0xf6, 0x34, 0x1e, 0x90,
464 | 0x40, 0xa2, 0x0c, 0x85, 0xd6, 0x5b, 0xaa, 0xb5, 0x4d, 0xd5, 0xa4, 0xeb,
465 | 0x90, 0xd8, 0xbf, 0x73, 0x2e, 0xbe, 0xe6, 0xe2, 0x66, 0xda, 0x78, 0x5a,
466 | 0x56, 0x1f, 0x1f, 0xfb, 0xd8, 0xc7, 0xe7, 0x6e, 0x8b, 0x81, 0xd7, 0xe6,
467 | 0xd3, 0x38, 0x74, 0x88, 0xac, 0xfd, 0xee, 0x9f, 0xb3, 0xff, 0x13, 0xc9,
468 | 0x52, 0xb3, 0xef, 0xb3, 0x64, 0xaa, 0x82, 0x3d, 0x1e, 0x35, 0x22, 0x15,
469 | 0x7b, 0xbe, 0x17, 0x99, 0x44, 0x08, 0x53, 0x15, 0xda, 0x11, 0x9e, 0xae,
470 | 0x23, 0x2f, 0xfd, 0xe0, 0x28, 0xe1, 0x57, 0x23, 0xf7, 0x77, 0x10, 0xde,
471 | 0xd5, 0xc5, 0x7a, 0x2e, 0xef, 0x2f, 0xaf, 0x99, 0xab, 0xfc, 0xcc, 0xd3,
472 | 0x10, 0x7e, 0x1b, 0xd6, 0x84, 0x0e, 0x93, 0x27, 0x29, 0x2c, 0xfa, 0x65,
473 | 0x0b, 0xbb, 0x73, 0x93, 0xf1, 0x16, 0xf6, 0x27, 0x44, 0xd8, 0x42, 0xe2,
474 | 0x10, 0x4d, 0xbf, 0x1a, 0x44, 0x0d, 0x3b, 0x48, 0xea, 0xa6, 0x68, 0x54,
475 | 0x27, 0x09, 0xd5, 0x91, 0x43, 0xd4, 0x26, 0xc3, 0x86, 0xe4, 0x39, 0xa8,
476 | 0xb3, 0xc9, 0x87, 0xbe, 0xf4, 0x6d, 0x38, 0x50, 0xc8, 0x8e, 0x01, 0xef,
477 | 0x59, 0x63, 0x5e, 0x0b, 0xa4, 0xe5, 0x89, 0xcb, 0xee, 0x98, 0x7d, 0x41,
478 | 0x1e, 0x52, 0x70, 0x75, 0x3e, 0x7a, 0x46, 0x06, 0x70, 0x66, 0xd2, 0x9b,
479 | 0x09, 0x0e, 0xcc, 0xea, 0x19, 0x77, 0xd0, 0x9f, 0x9e, 0x9b, 0x43, 0x6e,
480 | 0x99, 0x61, 0xc7, 0x08, 0x4a, 0xb0, 0x16, 0xeb, 0xeb, 0xc5, 0x76, 0x95,
481 | 0x44, 0x1f, 0xb6, 0x52, 0xfc, 0x29, 0x76, 0xa2, 0xdc, 0xc1, 0x47, 0x55,
482 | 0xa8, 0xc4, 0x34, 0xbf, 0x46, 0xa4, 0x87, 0x3b, 0x8f, 0x52, 0x75, 0x8f,
483 | 0xf9, 0xd4, 0xa7, 0x9d, 0x81, 0x35, 0x18, 0x93, 0x9d, 0x74, 0xd1, 0xdd,
484 | 0x83, 0x3e, 0x53, 0xcf, 0x3f, 0x12, 0x8e, 0x1c, 0x0e, 0x6f, 0xc4, 0x64,
485 | 0xf2, 0xf5, 0xf2, 0xd3, 0xe5, 0x89, 0x28, 0x8b, 0x95, 0xe4, 0xf8, 0xe1,
486 | 0x4d, 0x21, 0x4b, 0xb1, 0xdf, 0x16, 0xf0, 0x99, 0xcb, 0xad, 0x1c, 0x28,
487 | 0x30, 0xb1, 0x97, 0x22, 0xcf, 0xee, 0x88, 0xca, 0x59, 0x2e, 0x67, 0xb7,
488 | 0x18, 0x99, 0xc3, 0x87, 0x99, 0x28, 0xb7, 0x20, 0xf0, 0x8a, 0x28, 0x1a,
489 | 0x3a, 0x28, 0x9a, 0x2b, 0xec, 0x27, 0x60, 0xfe, 0x62, 0x05, 0xfa, 0x76,
490 | 0xb1, 0x59, 0x4a, 0x3c, 0x93, 0xfc, 0x10, 0x93, 0xc8, 0x2a, 0xea, 0x45,
491 | 0x73, 0xe2, 0x87, 0x9b, 0x24, 0xa5, 0xc5, 0x69, 0x18, 0x4a, 0xe2, 0xab,
492 | 0xcc, 0x2e, 0xc8, 0xfd, 0xc7, 0x4b, 0x38, 0xbf, 0x8d, 0x5c, 0x28, 0xcd,
493 | 0x32, 0x88, 0x5b, 0x3d, 0xa8, 0xd4, 0x54, 0x2b, 0x94, 0x33, 0x09, 0xb0,
494 | 0x9d, 0x45, 0x61, 0x9d, 0x38, 0xb3, 0x55, 0x39, 0x3e, 0xa8, 0x90, 0x61,
495 | 0xc2, 0xcb, 0xae, 0x34, 0xc8, 0xa5, 0x73, 0xd1, 0x22, 0xe2, 0x04, 0xb8,
496 | 0xa8, 0x8d, 0x1f, 0x95, 0xd1, 0xf8, 0xf8, 0x0c, 0xbe, 0xa7, 0xfc, 0x34,
497 | 0x09, 0x2d, 0x70, 0x9d, 0x4c, 0x45, 0x03, 0x77, 0x67, 0x87, 0x70, 0xe3,
498 | 0x57, 0xd9, 0x2d, 0xde, 0x53, 0x85, 0xed, 0x56, 0x2e, 0x85, 0x58, 0x67,
499 | 0x77, 0xf8, 0x2a, 0x8b, 0x36, 0xe2, 0x48, 0x59, 0xaf, 0xf1, 0x17, 0xe1,
500 | 0x78, 0x1a, 0xad, 0xaf, 0xe7, 0xa8, 0x2e, 0x04, 0x3c, 0xa6, 0x4b, 0x09,
501 | 0x63, 0x95, 0x23, 0xc4, 0xf8, 0x19, 0x65, 0x21, 0x7c, 0x98, 0x05, 0x46,
502 | 0xe8, 0x23, 0x1a, 0x9a, 0x95, 0xac, 0x69, 0x37, 0x03, 0xe1, 0x49, 0x50,
503 | 0xd1, 0xb3, 0x8f, 0xf9, 0x62, 0x39, 0x4f, 0xa8, 0xa7, 0x19, 0xeb, 0x58,
504 | 0xfd, 0xef, 0x54, 0x2e, 0xe8, 0x26, 0x2a, 0x57, 0x30, 0x0c, 0xc1, 0xc7,
505 | 0xa4, 0xfc, 0xb6, 0xa8, 0xf2, 0x24, 0x9a, 0x4c, 0xe8, 0xd0, 0xba, 0xdc,
506 | 0xe2, 0x81, 0x3a, 0x4e, 0xef, 0xa9, 0x3d, 0x44, 0x9f, 0x2f, 0xbe, 0x0f,
507 | 0x44, 0x29, 0x61, 0xb9, 0x7e, 0x83, 0xdb, 0xed, 0x78, 0x56, 0x0e, 0x01,
508 | 0xc2, 0xea, 0xfb, 0x74, 0x2c, 0xbd, 0xa4, 0xb8, 0x13, 0x48, 0xd6, 0x5c,
509 | 0x06, 0xc6, 0x8a, 0x3e, 0x0f, 0x43, 0x67, 0x02, 0x2d, 0x8a, 0x7b, 0x18,
510 | 0x19, 0x46, 0x62, 0x39, 0xd1, 0xb6, 0x4c, 0x1c, 0x0f, 0x68, 0x5b, 0x26,
511 | 0x2d, 0x94, 0x7c, 0xd7, 0xfb, 0xe1, 0x29, 0xe2, 0x56, 0x25, 0x18, 0x46,
512 | 0x75, 0xfa, 0x8c, 0xfc, 0xa3, 0x4c, 0x05, 0x80, 0xd4, 0x24, 0x1f, 0xab,
513 | 0x44, 0x9b, 0xa0, 0xe8, 0x23, 0xfa, 0x18, 0xf6, 0xa0, 0x50, 0xaf, 0x89,
514 | 0xde, 0xc0, 0xe0, 0x1e, 0xa2, 0xf4, 0x29, 0x6a, 0xe7, 0xc8, 0xa9, 0xa8,
515 | 0x0e, 0x2d, 0x06, 0x42, 0xe9, 0xec, 0xd3, 0x01, 0x84, 0xb0, 0x6e, 0x87,
516 | 0xf1, 0x01, 0x50, 0x4f, 0x74, 0x5e, 0x99, 0x7b, 0x08, 0xa5, 0x02, 0xec,
517 | 0x87, 0xd6, 0x56, 0x58, 0x07, 0x50, 0x22, 0x50, 0x3f, 0x74, 0x4e, 0x21,
518 | 0x66, 0x00, 0x1f, 0x41, 0x29, 0xa9, 0xfa, 0x28, 0x9b, 0x2e, 0x4c, 0x76,
519 | 0x97, 0x19, 0xe7, 0x5a, 0x6f, 0x76, 0x24, 0x75, 0x7b, 0x51, 0x0d, 0x76,
520 | 0x2b, 0xff, 0xcc, 0x8b, 0xfd, 0xda, 0x75, 0x87, 0x4c, 0xdc, 0x05, 0x24,
521 | 0x90, 0x1c, 0xef, 0xf3, 0xc5, 0x2c, 0xc7, 0x90, 0xf2, 0xeb, 0x37, 0xa9,
522 | 0xed, 0xad, 0x59, 0x6f, 0x4c, 0x93, 0xed, 0xc3, 0x67, 0x5e, 0xbf, 0x06,
523 | 0x39, 0x68, 0xb6, 0x78, 0x73, 0xc3, 0x40, 0xbb, 0x6f, 0x98, 0x50, 0x10,
524 | 0x98, 0x9a, 0x59, 0xbb, 0xd5, 0x81, 0x5d, 0x09, 0x19, 0xf3, 0x63, 0x1a,
525 | 0xac, 0xe7, 0xe8, 0x9f, 0x98, 0xd6, 0xa5, 0xd9, 0xc7, 0x88, 0xca, 0xb7,
526 | 0x29, 0x28, 0xbf, 0x26, 0xc4, 0x69, 0x9d, 0x23, 0xb0, 0x91, 0x97, 0x99,
527 | 0xbf, 0xc7, 0xd0, 0x7d, 0xe5, 0x15, 0x89, 0x79, 0x08, 0xe2, 0xb8, 0xbe,
528 | 0xfc, 0xb6, 0xe2, 0x5d, 0x2d, 0xd1, 0x0a, 0x98, 0x56, 0xee, 0x36, 0x1d,
529 | 0x1e, 0x69, 0x57, 0xd1, 0x4a, 0x12, 0x68, 0x4c, 0x29, 0xac, 0xa6, 0xab,
530 | 0x5a, 0x70, 0x09, 0x50, 0x79, 0xc5, 0x40, 0xa0, 0xa9, 0x74, 0x61, 0xaf,
531 | 0xb2, 0xbd, 0x8e, 0xc0, 0x99, 0xac, 0x1d, 0xc0, 0x9b, 0x69, 0x7c, 0xe4,
532 | 0x14, 0x03, 0x3c, 0xc6, 0x3c, 0x65, 0x75, 0x8c, 0x3a, 0x86, 0xca, 0x59,
533 | 0xdf, 0x8b, 0xcd, 0x16, 0xcc, 0x00, 0xd0, 0xc0, 0xfa, 0x2e, 0x8e, 0x6e,
534 | 0x8a, 0x46, 0x2d, 0xb9, 0x0b, 0xcf, 0xc8, 0x46, 0x2f, 0x7b, 0xa0, 0xc1,
535 | 0x53, 0xe1, 0xb2, 0x0a, 0x35, 0x89, 0xc9, 0xd5, 0x8f, 0xab, 0xe9, 0x74,
536 | 0x72, 0x32, 0x7d, 0x31, 0x3d, 0x8f, 0xde, 0x9d, 0x4d, 0xff, 0xfe, 0x1c,
537 | 0x1e, 0x4f, 0x00, 0x47, 0x59, 0x25, 0xa6, 0x9b, 0x0d, 0x31, 0xba, 0xf9,
538 | 0xa3, 0x08, 0x0b, 0x35, 0x4a, 0x30, 0x27, 0xe7, 0xe2, 0xa5, 0x58, 0x2c,
539 | 0x97, 0xf2, 0x26, 0x5b, 0x92, 0x81, 0x88, 0x56, 0x25, 0xcd, 0xce, 0x68,
540 | 0x26, 0x77, 0xd8, 0x87, 0xda, 0x69, 0x67, 0xf3, 0xa7, 0x2a, 0x4c, 0xc4,
541 | 0x9d, 0xf9, 0xc6, 0x0c, 0xde, 0x7d, 0x68, 0xe8, 0x81, 0x51, 0x5c, 0x66,
542 | 0x2f, 0xfd, 0x14, 0xd0, 0x18, 0x0c, 0xc7, 0xf8, 0xe9, 0xbd, 0x1e, 0xaa,
543 | 0x2a, 0xef, 0xb9, 0x9b, 0x2d, 0x7c, 0xa7, 0x4c, 0x47, 0x13, 0xce, 0xb2,
544 | 0x48, 0xd0, 0x50, 0x71, 0x11, 0x20, 0x4e, 0x41, 0x5f, 0x28, 0x47, 0x84,
545 | 0x42, 0xca, 0x7f, 0xf7, 0xb9, 0x94, 0x4b, 0xfe, 0x44, 0xcd, 0x2e, 0xb8,
546 | 0x6c, 0x9e, 0xc3, 0x15, 0xb0, 0x72, 0x78, 0x21, 0x9a, 0x6d, 0x89, 0xfb,
547 | 0x0a, 0xac, 0x80, 0x9d, 0xa0, 0xf7, 0x5c, 0xd8, 0xf6, 0xa6, 0x4f, 0x20,
548 | 0x8d, 0x3f, 0xa8, 0x37, 0x7d, 0xcd, 0xb2, 0xf5, 0x4c, 0x2e, 0xbd, 0x39,
549 | 0xab, 0x2d, 0x0c, 0x33, 0xb3, 0xb1, 0x34, 0x1a, 0x8f, 0x07, 0xc6, 0x36,
550 | 0xea, 0xac, 0xd7, 0x5d, 0x00, 0x67, 0x9e, 0x29, 0xac, 0x2a, 0xa9, 0x7e,
551 | 0x38, 0x46, 0xcd, 0xe1, 0x42, 0x13, 0xd8, 0xb6, 0xd1, 0xc2, 0x5a, 0x40,
552 | 0xb9, 0x35, 0xca, 0xc8, 0x77, 0xb1, 0x6b, 0xbd, 0xd4, 0xdb, 0x4a, 0xa1,
553 | 0x70, 0xa1, 0x68, 0xc4, 0x0b, 0x03, 0x84, 0xee, 0xd6, 0xcb, 0x22, 0x9b,
554 | 0x03, 0x79, 0x2a, 0xde, 0x0f, 0x44, 0x0a, 0xc3, 0x47, 0xce, 0x60, 0x0d,
555 | 0x5d, 0xc1, 0xc5, 0xc2, 0x24, 0x62, 0xf8, 0x6a, 0x73, 0x3c, 0xc2, 0x46,
556 | 0xf7, 0xb2, 0x9b, 0x6e, 0xe7, 0xa2, 0x9f, 0x98, 0x0f, 0xa9, 0x0a, 0x4c,
557 | 0x2b, 0xab, 0x4d, 0x83, 0x80, 0x64, 0xa0, 0xfe, 0xd8, 0x84, 0x31, 0x53,
558 | 0xea, 0x91, 0xa4, 0x4a, 0x40, 0xd7, 0x0a, 0x94, 0x6d, 0x63, 0xb4, 0xb9,
559 | 0x8f, 0x02, 0x58, 0x03, 0x13, 0xab, 0x8d, 0xe0, 0xd2, 0xd4, 0xc4, 0x6f,
560 | 0x07, 0xd0, 0xf1, 0x74, 0xf0, 0xa5, 0x4b, 0xf4, 0x4a, 0x8c, 0xaa, 0xc3,
561 | 0xf5, 0x71, 0x1e, 0x6f, 0x9a, 0xe0, 0xc9, 0xa5, 0xeb, 0xcb, 0xf4, 0xf6,
562 | 0xf0, 0x3f, 0xab, 0x18, 0x35, 0x44, 0x8c, 0x58, 0x00, 0x00
563 | };
564 | const unsigned int index_htm_gz_len = 6730;
565 |
--------------------------------------------------------------------------------
/percentEncode.h:
--------------------------------------------------------------------------------
1 | // https://www.w3schools.com/tags/ref_urlencode.asp
2 |
3 | // https://www.fon.hum.uva.nl/praat/manual/Special_symbols.html
4 |
5 | String percentEncode(const char* plaintext) {
6 | String result{};
7 | uint32_t cnt{ 0 };
8 | while (plaintext[cnt] != 0) {
9 | if (plaintext[cnt] > 0x7F || plaintext[cnt] < 0x20) {
10 | switch (plaintext[cnt]) {
11 |
12 | case 0xC2:
13 | {
14 | const uint8_t firstByte = plaintext[cnt];
15 | cnt++;
16 | const uint8_t secondByte = plaintext[cnt];
17 | switch (secondByte) {
18 | case 0xA0 ... 0xBF:
19 | {
20 | result.concat((char)firstByte);
21 | result.concat((char)secondByte);
22 | }
23 | break;
24 | default:
25 | {
26 | result.concat("?");
27 | log_e("Invalid 16-bit utf8 sequence. Dropped 2 bytes.");
28 | }
29 | }
30 | }
31 | break;
32 |
33 | case 0xC3:
34 | {
35 | const uint8_t firstByte = plaintext[cnt];
36 | cnt++;
37 | const uint8_t secondByte = plaintext[cnt];
38 | switch (secondByte) {
39 | case 0x80 ... 0xBF:
40 | {
41 | result.concat((char)firstByte);
42 | result.concat((char)secondByte);
43 | }
44 | break;
45 | default:
46 | {
47 | result.concat("?");
48 | log_e("Invalid 16-bit utf8 sequence. Dropped 2 bytes.");
49 | }
50 | }
51 | }
52 | break;
53 |
54 | case 0xC9:
55 | result.concat("É"); // É
56 | break;
57 |
58 | case 0xE1:
59 | result.concat("á"); // á
60 | break;
61 |
62 | case 0xE4:
63 | result.concat("ä"); // ä
64 | break;
65 |
66 | case 0xE7:
67 | result.concat("ç"); // ç
68 | break;
69 |
70 | case 0xE8:
71 | result.concat("è"); // è
72 | break;
73 |
74 | case 0xE9:
75 | result.concat("é"); // é
76 | break;
77 |
78 | case 0xEA:
79 | result.concat("ê"); // ê
80 | break;
81 |
82 | case 0xEB:
83 | result.concat("ë"); // ë
84 | break;
85 |
86 | case 0xED:
87 | result.concat("í"); // í
88 | break;
89 |
90 | // WIP
91 | case 0xEF: //Byte Order Mark -> https://en.wikipedia.org/wiki/Byte_order_mark - see UTF-8 on that page - seen on 'SUBLIME pure jazz'
92 | {
93 | cnt++;
94 | const uint8_t secondByte = plaintext[cnt];
95 | if (0xBB != secondByte) {
96 | result.concat("?");
97 | log_e("Invalid byte sequence. Dropped 2 bytes.");
98 | break;
99 | }
100 | cnt++;
101 | const uint8_t thirdByte = plaintext[cnt];
102 | if (0xBF != thirdByte) {
103 | result.concat("?");
104 | log_e("Invalid byte sequence. Dropped 3 bytes.");
105 | break;
106 | }
107 | /* if arrived here, we have the sequence 0xEF,0xBB,0xBF which is a BOM and codes for no output */
108 | ESP_LOGD(TAG, "Byte Order Mark skipped");
109 | }
110 | break;
111 |
112 | case 0xF3:
113 | result.concat("ó "); // ó
114 | break;
115 |
116 | case 0xF6:
117 | result.concat("ö"); // ö
118 | break;
119 |
120 | case 0xFC:
121 | result.concat("ü"); // ü
122 | break;
123 |
124 | default:
125 | result.concat("?");
126 | log_w("ERROR: Unhandled char 0x%x", plaintext[cnt]);
127 | }
128 | } else
129 | result.concat(plaintext[cnt]);
130 | cnt++;
131 | }
132 | ESP_LOGD(TAG, "Input str: %s", plaintext);
133 | ESP_LOGD(TAG, "Returning html encoded str: %s", result.c_str());
134 | return result;
135 | }
136 |
--------------------------------------------------------------------------------
/playList.cpp:
--------------------------------------------------------------------------------
1 | #include "playList.h"
2 |
3 | String& playList_t::toString(String& s) {
4 | s = "playlist\n";
5 | if (list.size()) {
6 | for (const auto& item : list) {
7 | switch (item.type) {
8 |
9 | case HTTP_FILE:
10 | s.concat(item.url.substring(item.url.lastIndexOf("/") + 1) + "\n" + typeStr[item.type] + "\n");
11 | break;
12 |
13 | case HTTP_PRESET:
14 | s.concat(preset[item.index].name + "\n" + typeStr[item.type] + "\n");
15 | break;
16 |
17 | case HTTP_STREAM:
18 | case HTTP_FAVORITE:
19 | s.concat(item.name + "\n" + typeStr[item.type] + "\n");
20 | break;
21 |
22 | default:
23 | log_e("ERROR! Enum item is unhandled!");
24 | break;
25 | }
26 | }
27 | }
28 | return s;
29 | }
30 |
--------------------------------------------------------------------------------
/playList.h:
--------------------------------------------------------------------------------
1 | #ifndef __PLAYLIST_H
2 | #define __PLAYLIST_H
3 |
4 | #include
5 | #include
6 | #include "presets.h"
7 |
8 | #define PLAYLIST_MAX_ITEMS 100
9 |
10 | enum streamType { HTTP_FILE,
11 | HTTP_STREAM,
12 | HTTP_FAVORITE,
13 | HTTP_PRESET,
14 | SDCARD_FILE };
15 | static const char* typeStr[] = { "FILE", "STREAM", "FAVO", "PRESET", "SDCARD" };
16 |
17 | struct playListItem {
18 | streamType type;
19 | String name;
20 | String url;
21 | uint32_t index;
22 | };
23 |
24 | class playList_t {
25 |
26 | public:
27 | playList_t() {
28 | ESP_LOGD(TAG, "allocating %i items", PLAYLIST_MAX_ITEMS);
29 | list.reserve(PLAYLIST_MAX_ITEMS);
30 | }
31 | ~playList_t() {
32 | list.clear();
33 | }
34 | int size() {
35 | return list.size();
36 | }
37 | bool isUpdated{ false };
38 | void get(const uint32_t index, playListItem& item) {
39 | item = (index < list.size()) ? list[index] : (playListItem){};
40 | }
41 | void add(const playListItem& item) {
42 | if (list.size() < PLAYLIST_MAX_ITEMS) {
43 | list.push_back(item);
44 | isUpdated = true;
45 | }
46 | }
47 | void remove(const uint32_t index) {
48 | if (list.size() > index) {
49 | list.erase(list.begin() + index);
50 | isUpdated = true;
51 | }
52 | }
53 | void clear() {
54 | if (list.size()) {
55 | list.clear();
56 | isUpdated = true;
57 | }
58 | }
59 | String& toString(String& s);
60 |
61 | private:
62 | std::vector list;
63 | };
64 |
65 | #endif
66 |
--------------------------------------------------------------------------------
/presets.h:
--------------------------------------------------------------------------------
1 | struct source {
2 | const String name;
3 | const String url;
4 | };
5 |
6 | // https://hendrikjansen.nl/henk/streaming.html
7 |
8 | const source preset[]{
9 | { "NPO Radio1", "http://icecast.omroep.nl/radio1-bb-mp3" },
10 | { "NPO Radio2", "http://icecast.omroep.nl/radio2-bb-mp3" },
11 | { "NPO Radio2 Soul&Jazz", "http://icecast.omroep.nl/radio6-bb-mp3" },
12 | { "NPO 3FM", "http://icecast.omroep.nl/3fm-bb-mp3" },
13 | { "NPO 3FM Alternative", "http://icecast.omroep.nl/3fm-alternative-mp3" },
14 | { "NPO Radio4", "http://icecast.omroep.nl/radio4-bb-mp3" },
15 | { "NPO Radio5", "http://icecast.omroep.nl/radio5-bb-mp3" },
16 | { "538 Dance Department", "http://22193.live.streamtheworld.com/TLPSTR01.mp3" },
17 | { "Absoluut FM", "http://absoluutfm.stream.laut.fm/absoluutfm" },
18 | { "Amsterdam Funk Channel", "http://stream.afc.fm:8504/listen.pls" },
19 | { "Radio 10 Disco Classics", "http://19993.live.streamtheworld.com/RADIO10.mp3" },
20 | { "Sublime Soul", "http://20863.live.streamtheworld.com/SUBLIMESOUL.mp3" },
21 | { "XXL Stenders", "http://streams.robstenders.nl:8063/bonanza_mp3" },
22 | { "RadioEins", "http://rbb-edge-20b4-fra-lg-cdn.cast.addradio.de/rbb/radioeins/live/mp3/mid?ar-distributor=f0a0" },
23 | { "Tekno1", "http://212.83.149.66:8591" },
24 | { "BBC radio 1", "http://stream.live.vc.bbcmedia.co.uk/bbc_radio_one" },
25 | { "BBC radio 2", "http://stream.live.vc.bbcmedia.co.uk/bbc_radio_two" },
26 | { "BBC radio 3", "http://stream.live.vc.bbcmedia.co.uk/bbc_radio_three" },
27 | { "BBC radio 4", "http://stream.live.vc.bbcmedia.co.uk/bbc_radio_fourfm" },
28 | { "BBC radio 5", "http://stream.live.vc.bbcmedia.co.uk/bbc_radio_five_live_online_nonuk" },
29 | { "BBC radio 6", "http://stream.live.vc.bbcmedia.co.uk/bbc_6music" },
30 | };
31 |
--------------------------------------------------------------------------------
/system_setup.h:
--------------------------------------------------------------------------------
1 | /* system setup file for eStreamPlayer32 */
2 |
3 | const char* SSID = "xxx";
4 | const char* PSK = "xxx";
5 |
6 | /* uncomment one of the following lines to compile for a board or dac */
7 | //#define GENERIC_I2S_DAC
8 | //#define A1S_AUDIO_KIT
9 | #define M5STACK_NODE
10 |
11 | #if defined(GENERIC_I2S_DAC)
12 | /* I2S pins on Cellie's dev board */
13 | #define I2S_BCK 21
14 | #define I2S_WS 26
15 | #define I2S_DOUT 22
16 | #endif //GENERIC_I2S_DAC
17 |
18 | /* SCRIPT_URL should point to the php script on the music file server */
19 | const char* SCRIPT_URL = "http://192.168.0.108/eSP32_vs1053.php";
20 |
21 | /* If SET_STATIC_IP is set to true then STATIC_IP, GATEWAY, SUBNET and PRIMARY_DNS have to be set to some sane values */
22 | const bool SET_STATIC_IP = false;
23 |
24 | const IPAddress STATIC_IP(192, 168, 0, 10); /* This should be outside your router dhcp range! */
25 | const IPAddress GATEWAY(192, 168, 0, 1); /* Set to your gateway ip address */
26 | const IPAddress SUBNET(255, 255, 255, 0); /* Usually 255,255,255,0 check in your router or pc connected to the same network */
27 | const IPAddress PRIMARY_DNS(192, 168, 0, 30); /* Check in your router */
28 | const IPAddress SECONDARY_DNS(0, 0, 0, 0); /* Check in your router */
29 |
30 | const char* NTP_POOL = "nl.pool.ntp.org";
31 |
32 | const char* TIMEZONE = "CET-1CEST,M3.5.0/2,M10.5.0/3"; /* Central European Time - see http://www.remotemonitoringsystems.ca/time-zone-abbreviations.php */
33 |
--------------------------------------------------------------------------------