├── 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 | ![m5screen](https://user-images.githubusercontent.com/24290108/104571384-4e3fa400-5653-11eb-96ae-2202fb19acbf.png) 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 | ![home](img/overlay.png) 33 | 34 | #### LIBRARY tab 35 | 36 | ![library](img/library.png) 37 | 38 | #### WEBRADIO tab 39 | 40 | ![webradio](img/webradio.png) 41 | 42 | #### FAVORITES tab 43 | 44 | ![favorites](img/favorites.png) 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 ''; 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 ''; 39 | } 40 | foreach(glob($path.$validFiles,GLOB_BRACE)as$filename){ 41 | $pieces=explode('/',$filename); 42 | echo ''; 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 |
230 |

231 |

232 |
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 | --------------------------------------------------------------------------------