Music and controls!!!!! 🎉

This commit is contained in:
Emi Simpson 2022-01-03 18:19:10 -05:00
parent 02fbd15f44
commit c211e79658
Signed by: Emi
GPG key ID: A12F2C2FFDC3D847
10 changed files with 811 additions and 64 deletions

4
.gitmodules vendored Normal file
View file

@ -0,0 +1,4 @@
[submodule "rodio"]
path = rodio
url = https://github.com/Alch-Emi/rodio.git
branch = new-from-format-reader

406
Cargo.lock generated
View file

@ -36,6 +36,28 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "alsa"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75c4da790adcb2ce5e758c064b4f3ec17a30349f9961d3e5e6c9688b052a9e18"
dependencies = [
"alsa-sys",
"bitflags",
"libc",
"nix 0.20.2",
]
[[package]]
name = "alsa-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "andrew"
version = "0.3.1"
@ -72,9 +94,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "arrayvec"
version = "0.7.2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
checksum = "269d0f5e68353a7cab87f81e7c736adc008d279a36ebc6a05dfe01193a89f0c9"
[[package]]
name = "ash"
@ -207,6 +229,25 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bindgen"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
]
[[package]]
name = "bit-set"
version = "0.5.2"
@ -286,6 +327,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "cache-padded"
version = "1.2.0"
@ -321,6 +368,21 @@ dependencies = [
"jobserver",
]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom 5.1.2",
]
[[package]]
name = "cfg-expr"
version = "0.8.1"
@ -348,6 +410,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "clang-sys"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa785e9017cb8e8c8045e3f096b7d1ebc4d7337cceccdca8d678a27f788ac133"
dependencies = [
"glob",
"libc",
"libloading 0.6.7",
]
[[package]]
name = "clipboard-win"
version = "4.2.2"
@ -435,6 +508,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "combine"
version = "4.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5"
dependencies = [
"bytes",
"memchr",
]
[[package]]
name = "concurrent-queue"
version = "1.2.2"
@ -544,6 +627,50 @@ dependencies = [
"objc",
]
[[package]]
name = "coreaudio-rs"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88"
dependencies = [
"bitflags",
"coreaudio-sys",
]
[[package]]
name = "coreaudio-sys"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b7e3347be6a09b46aba228d6608386739fb70beff4f61e07422da87b0bb31fa"
dependencies = [
"bindgen",
]
[[package]]
name = "cpal"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98f45f0a21f617cd2c788889ef710b63f075c949259593ea09c826f1e47a2418"
dependencies = [
"alsa",
"core-foundation-sys 0.8.3",
"coreaudio-rs",
"jni",
"js-sys",
"lazy_static",
"libc",
"mach",
"ndk 0.3.0",
"ndk-glue 0.3.0",
"nix 0.20.2",
"oboe",
"parking_lot",
"stdweb",
"thiserror",
"web-sys",
"winapi 0.3.9",
]
[[package]]
name = "crc32fast"
version = "1.3.0"
@ -669,6 +796,7 @@ dependencies = [
"iced_native",
"image",
"rfd",
"rodio",
"symphonia",
]
@ -1306,6 +1434,12 @@ dependencies = [
"system-deps",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "glow"
version = "0.7.2"
@ -1671,6 +1805,20 @@ dependencies = [
"either",
]
[[package]]
name = "jni"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
dependencies = [
"cesu8",
"combine",
"jni-sys",
"log",
"thiserror",
"walkdir",
]
[[package]]
name = "jni-sys"
version = "0.3.0"
@ -1843,6 +1991,15 @@ dependencies = [
"sid",
]
[[package]]
name = "mach"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
dependencies = [
"libc",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
@ -1997,7 +2154,32 @@ checksum = "5eb167c1febed0a496639034d0c76b3b74263636045db5489eee52143c246e73"
dependencies = [
"jni-sys",
"ndk-sys",
"num_enum",
"num_enum 0.4.3",
"thiserror",
]
[[package]]
name = "ndk"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab"
dependencies = [
"jni-sys",
"ndk-sys",
"num_enum 0.5.6",
"thiserror",
]
[[package]]
name = "ndk"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d64d6af06fde0e527b1ba5c7b79a6cc89cfc46325b0b2887dffe8f70197e0c3c"
dependencies = [
"bitflags",
"jni-sys",
"ndk-sys",
"num_enum 0.5.6",
"thiserror",
]
@ -2010,7 +2192,35 @@ dependencies = [
"lazy_static",
"libc",
"log",
"ndk",
"ndk 0.2.1",
"ndk-macro",
"ndk-sys",
]
[[package]]
name = "ndk-glue"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5caf0c24d51ac1c905c27d4eda4fa0635bbe0de596b8f79235e0b17a4d29385"
dependencies = [
"lazy_static",
"libc",
"log",
"ndk 0.3.0",
"ndk-macro",
"ndk-sys",
]
[[package]]
name = "ndk-glue"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e9e94628f24e7a3cb5b96a2dc5683acd9230bf11991c2a1677b87695138420"
dependencies = [
"lazy_static",
"libc",
"log",
"ndk 0.4.0",
"ndk-macro",
"ndk-sys",
]
@ -2022,7 +2232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d"
dependencies = [
"darling",
"proc-macro-crate",
"proc-macro-crate 0.1.5",
"proc-macro2",
"quote",
"syn",
@ -2083,6 +2293,16 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "nom"
version = "7.1.0"
@ -2094,6 +2314,17 @@ dependencies = [
"version_check",
]
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@ -2152,7 +2383,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca565a7df06f3d4b485494f25ba05da1435950f4dc263440eda7a6fa9b8e36e4"
dependencies = [
"derivative",
"num_enum_derive",
"num_enum_derive 0.4.3",
]
[[package]]
name = "num_enum"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad"
dependencies = [
"num_enum_derive 0.5.6",
]
[[package]]
@ -2161,7 +2401,19 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d"
dependencies = [
"proc-macro-crate",
"proc-macro-crate 0.1.5",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num_enum_derive"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21"
dependencies = [
"proc-macro-crate 1.1.0",
"proc-macro2",
"quote",
"syn",
@ -2206,6 +2458,29 @@ dependencies = [
"objc",
]
[[package]]
name = "oboe"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e15e22bc67e047fe342a32ecba55f555e3be6166b04dd157cd0f803dfa9f48e1"
dependencies = [
"jni",
"ndk 0.4.0",
"ndk-glue 0.4.0",
"num-derive",
"num-traits",
"oboe-sys",
]
[[package]]
name = "oboe-sys"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "338142ae5ab0aaedc8275aa8f67f460e43ae0fca76a695a742d56da0a269eadc"
dependencies = [
"cc",
]
[[package]]
name = "once_cell"
version = "1.9.0"
@ -2301,6 +2576,12 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -2384,6 +2665,16 @@ dependencies = [
"toml",
]
[[package]]
name = "proc-macro-crate"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83"
dependencies = [
"thiserror",
"toml",
]
[[package]]
name = "proc-macro2"
version = "1.0.36"
@ -2511,6 +2802,21 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rfd"
version = "0.6.3"
@ -2534,6 +2840,14 @@ dependencies = [
"windows",
]
[[package]]
name = "rodio"
version = "0.14.0"
dependencies = [
"cpal",
"symphonia",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
@ -2631,6 +2945,12 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "shlex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]]
name = "sid"
version = "0.6.1"
@ -2779,6 +3099,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stdweb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
[[package]]
name = "storage-map"
version = "0.3.0"
@ -2826,16 +3152,15 @@ checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2"
[[package]]
name = "symphonia"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7e5f38aa07e792f4eebb0faa93cee088ec82c48222dd332897aae1569d9a4b7"
checksum = "9fae959d5ea7b4cd0cd8db3b899ec4f549b0d8a298694826a36ae7e5f19d4aa6"
dependencies = [
"lazy_static",
"symphonia-bundle-flac",
"symphonia-bundle-mp3",
"symphonia-codec-aac",
"symphonia-codec-pcm",
"symphonia-codec-vorbis",
"symphonia-core",
"symphonia-format-isomp4",
"symphonia-format-ogg",
@ -2845,9 +3170,9 @@ dependencies = [
[[package]]
name = "symphonia-bundle-flac"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "116e5412f5fb4e5d07efd6628d50d6fcd7a61ebef43d98f5012f3cf763b25d02"
checksum = "b237be42d0ff1ff64c6e073aea4f93985ca51de93b8279f16a4b006e3e5997af"
dependencies = [
"log",
"symphonia-core",
@ -2857,9 +3182,9 @@ dependencies = [
[[package]]
name = "symphonia-bundle-mp3"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec4d97c4a61ece4651751dddb393ebecb7579169d9e758ae808fe507a5250790"
checksum = "b9f596fe16d2ae06e9404558644b61e27e2dcfee6c511f559be4699c403283fa"
dependencies = [
"bitflags",
"lazy_static",
@ -2870,9 +3195,9 @@ dependencies = [
[[package]]
name = "symphonia-codec-aac"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd3d7ab37eb9b7df16ddedd7adb7cc382afe708ff078e525a14dc9b05e57558f"
checksum = "9e636422dccfb202b24f8066b8d3ebfa914bbc690dae78db52b54691ba916c3f"
dependencies = [
"lazy_static",
"log",
@ -2881,43 +3206,32 @@ dependencies = [
[[package]]
name = "symphonia-codec-pcm"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba1d54738758993546107e3a4be2c1da827f2d4489fcffee0fa47867254e44c7"
checksum = "021d8161b186bea81c7cf4a80c67c71fb53862cd9f426cd3e032ae09bdd42dec"
dependencies = [
"log",
"symphonia-core",
]
[[package]]
name = "symphonia-codec-vorbis"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a29ed6748078effb35a05064a451493a78038918981dc1a76bdf5a2752d441fa"
dependencies = [
"log",
"symphonia-core",
"symphonia-utils-xiph",
]
[[package]]
name = "symphonia-core"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa135e97be0f4a666c31dfe5ef4c75435ba3d355fd6a73d2100aa79b14c104c9"
checksum = "c1742e06f50b4a7ed7abee53433231e050a248b498cd0ae2c639c8a70b115001"
dependencies = [
"arrayvec 0.7.2",
"arrayvec 0.6.1",
"bitflags",
"bytemuck",
"byteorder",
"lazy_static",
"log",
]
[[package]]
name = "symphonia-format-isomp4"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feee3a7711e7ec1b7540756f3868bbb3cbb0d1195569b9bc26471a24a02f57b5"
checksum = "bf3c6b6ca3347caa22d72f04cfd509f0c32683b0dbe01d0cfb63fd2726ac6da5"
dependencies = [
"encoding_rs",
"log",
@ -2927,9 +3241,9 @@ dependencies = [
[[package]]
name = "symphonia-format-ogg"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7b2357288a79adfec532cfd86049696cfa5c58efeff83bd51687a528f18a519"
checksum = "4898eef85c5a05136e1f3b5ff1afe4423cd7642c8979ed007cc8924b9a4c18c1"
dependencies = [
"log",
"symphonia-core",
@ -2939,9 +3253,9 @@ dependencies = [
[[package]]
name = "symphonia-format-wav"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da3586e944a951f3ff19ae14d3f46643c063784f119bffb091fc536102909575"
checksum = "97e863d9a912ea518dfae664292e38b2b961a1eb780251eba89b60e3905fef37"
dependencies = [
"log",
"symphonia-core",
@ -2950,9 +3264,9 @@ dependencies = [
[[package]]
name = "symphonia-metadata"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5260599daba18d8fe905ca3eb3b42ba210529a6276886632412cc74984e79b1a"
checksum = "db5e36e38a7400f968569135e7ac0f8647de42e93905ad41c79d583aaeae565c"
dependencies = [
"encoding_rs",
"lazy_static",
@ -2962,9 +3276,9 @@ dependencies = [
[[package]]
name = "symphonia-utils-xiph"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a37026c6948ff842e0bf94b4008579cc71ab16ed0ff9ca70a331f60f4f1e1e9"
checksum = "47377d86d61acf4d5b1a054b8e7a7cac8266155577a5410e4d746aec6394c42a"
dependencies = [
"symphonia-core",
"symphonia-metadata",
@ -3629,8 +3943,8 @@ dependencies = [
"log",
"mio",
"mio-extras",
"ndk",
"ndk-glue",
"ndk 0.2.1",
"ndk-glue 0.2.1",
"ndk-sys",
"objc",
"parking_lot",
@ -3690,7 +4004,7 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7"
dependencies = [
"nom",
"nom 7.1.0",
]
[[package]]

View file

@ -20,9 +20,9 @@ iced_native = "0.4.0"
rfd = "0.6.3"
[dependencies.symphonia]
# Music decoding, playing, and metadata parsing
# Music decoding and metadata parsing
features = ["isomp4", "aac", "mp3"]
version = "0.4.0"
version = "0.3.0"
[dependencies.iced]
# Display windows & graphics
@ -33,3 +33,9 @@ version = "0.3.0"
# Display windows & graphics
features = ["smol"]
version = "0.3.0"
[dependencies.rodio]
# Playing audio
default-features = false
features = ["symphonia"]
path = "./rodio/"

1
rodio Submodule

@ -0,0 +1 @@
Subproject commit 706abafcadab2c0049d7a7724cae43f203ba0f67

View file

@ -1,3 +1,4 @@
use crate::controls::ControlsEvent;
use crate::load_song::load_song;
use crate::editor::Editor;
use crate::file_select::FileSelector;
@ -34,6 +35,7 @@ pub enum Message {
PromptForFile,
FileOpened(PathBuf),
Resized(u32, u32),
ControlsEvent(ControlsEvent),
Null,
}
@ -111,7 +113,12 @@ impl Application for DelyriumApp {
if let Some(lyrics) = self.lyrics_component.as_mut() {
lyrics.notify_resized(w, h);
}
}
},
Message::ControlsEvent(e) => {
if let Some(lyrics) = self.lyrics_component.as_mut() {
lyrics.handle_controls_event(e);
}
},
Message::Null => { },
}
@ -150,15 +157,21 @@ impl Application for DelyriumApp {
}
});
let fps30 = time::every(Duration::from_millis(1000 / 30)).map(|_| Message::Tick);
if self.lyrics_component.is_none() {
Subscription::batch([
runtime_events,
fps30
])
let is_animating = if let Some(editor) = &self.lyrics_component {
editor.is_animating()
} else {
runtime_events
}
true
};
let fps30 = if is_animating {
time::every(Duration::from_millis(1000 / 30)).map(|_| Message::Tick)
} else {
Subscription::none()
};
Subscription::batch([
runtime_events,
fps30
])
}
}

139
src/controls.rs Normal file
View file

@ -0,0 +1,139 @@
use iced::Length;
use iced::Color;
use symphonia::core::formats::FormatReader;
use crate::player::PlayerError;
use iced::canvas::event::Status;
use iced::canvas::Event;
use crate::styles::Theme;
use iced::canvas::Frame;
use iced::canvas::Geometry;
use iced::canvas::Cursor;
use iced::Rectangle;
use crate::app::Message;
use iced::canvas::Program;
use iced::Canvas;
use iced::mouse::{self, Button};
use crate::player::Player;
#[derive(Debug, Clone, Copy)]
pub enum ControlsEvent {
SeekPosition(f32),
TogglePlay,
}
enum ErrorState {
Error(PlayerError),
NoError {
player: Player,
has_device: bool
}
}
use ErrorState::*;
pub struct Controls {
error_state: ErrorState,
}
impl Controls {
pub fn new(song: Box<dyn FormatReader>) -> Self {
match Player::new(song) {
Ok(player) => {
Controls {
error_state: NoError {
has_device: player.has_output_device(),
player,
}
}
},
Err(e) => {
Controls {
error_state: Error(e)
}
}
}
}
pub fn view_progress(&mut self, theme: Theme) -> Canvas<Message, (&Controls, Theme)> {
Canvas::new((&*self, theme))
.width(Length::Units(50))
.height(Length::Fill)
}
pub fn handle_event(&mut self, event: ControlsEvent) {
if let NoError { player, has_device } = &mut self.error_state {
let result = match event {
ControlsEvent::SeekPosition(pos) => player.seek_percentage(pos),
ControlsEvent::TogglePlay => player.toggle_play(),
};
match result {
Ok(now_has_device) => {
*has_device = now_has_device;
},
Err(e) => {
self.error_state = Error(e);
},
};
}
}
pub fn is_playing(&self) -> bool {
if let NoError { player, has_device: true } = &self.error_state {
player.is_playing()
} else {
false
}
}
}
impl Program<Message> for (&Controls, Theme) {
fn draw(&self, bounds: Rectangle<f32>, _cursor: Cursor) -> Vec<Geometry> {
let mut frame = Frame::new(bounds.size());
match &self.0.error_state {
NoError { player, has_device: true } => {
let mut background = self.1.text_color;
background.a = 0.2;
frame.fill_rectangle(bounds.position(), bounds.size(), background);
let mut played_size = bounds.size();
played_size.height *= player.position_percent();
frame.fill_rectangle(bounds.position(), played_size, self.1.text_color);
},
NoError { player: _, has_device: false } => {
let mut background = self.1.text_color;
background.a = 0.1;
frame.fill_rectangle(bounds.position(), bounds.size(), background);
},
Error(e) => {
let background = Color {r: 1., g: 0.1, b: 0.1, a: 1.};
frame.fill_rectangle(bounds.position(), bounds.size(), background);
eprintln!("Error!!! {}", e.to_string());
}
}
vec![frame.into_geometry()]
}
fn update(&mut self, event: Event, bounds: Rectangle<f32>, cursor: Cursor) -> (Status, Option<Message>) {
match (event, cursor) {
(Event::Mouse(mouse::Event::ButtonReleased(Button::Left)), Cursor::Available(pos))
if bounds.contains(pos) => {
let sought = (pos.y - bounds.position().y) / bounds.size().height;
(Status::Captured, Some(Message::ControlsEvent(ControlsEvent::SeekPosition(sought))))
},
(Event::Mouse(mouse::Event::ButtonReleased(Button::Right)), Cursor::Available(pos))
if bounds.contains(pos) => {
// TODO! This should be somewhere intuitive
(Status::Captured, Some(Message::ControlsEvent(ControlsEvent::TogglePlay)))
},
_ => (Status::Ignored, None),
}
}
}

View file

@ -1,3 +1,5 @@
use crate::controls::ControlsEvent;
use crate::controls::Controls;
use crate::lyrics::Lyric;
use crate::lyrics::Lyrics;
use crate::app::Message;
@ -18,7 +20,7 @@ use image::GenericImageView;
pub struct Editor {
lyrics: Lyrics,
theme: Theme,
song: Box<dyn FormatReader>,
controls: Controls,
left_peri: Periphery,
rite_peri: Periphery,
}
@ -40,13 +42,18 @@ impl Editor {
let left_peri = Periphery::new(cover.clone(), true, size);
let rite_peri = Periphery::new(cover, false, size);
let controls = Controls::new(song);
Self {
lyrics: Lyrics::new(),
song, theme, left_peri, rite_peri,
controls, theme, left_peri, rite_peri,
}
}
// TODO: work on untangling this mess
pub fn handle_controls_event(&mut self, event: ControlsEvent) {
self.controls.handle_event(event)
}
pub fn insert_text(&mut self, text: String) {
self.lyrics.insert_text(text);
}
@ -60,6 +67,10 @@ impl Editor {
self.lyrics.current_line_mut()
}
pub fn is_animating(&self) -> bool {
self.controls.is_playing()
}
pub fn notify_resized(&mut self, w: u32, h: u32) {
self.left_peri.notify_resized(w, h);
self.rite_peri.notify_resized(w, h);
@ -70,6 +81,7 @@ impl Editor {
Container::new(
Row::new()
.push(self.left_peri.view())
.push(self.controls.view_progress(self.theme))
.push(self.lyrics.view(self.theme))
.push(self.rite_peri.view())
)

View file

@ -8,12 +8,10 @@ use std::ffi::OsStr;
use symphonia::default;
use symphonia::core::io::MediaSourceStream;
use symphonia::core::probe::{Hint, ProbeResult};
use symphonia::core::meta::MetadataRevision;
use symphonia::core::meta::StandardVisualKey;
use symphonia::core::formats::FormatReader;
pub fn load_song(path: &Path) -> Result<ProbeResult, LoadError> {
let codecs = default::get_codecs();
let probe = default::get_probe();
let file = OpenOptions::new()
@ -44,8 +42,9 @@ pub fn extract_cover(format: &mut dyn FormatReader) -> Option<DynamicImage>{
format.metadata()
.current()
.into_iter()
.flat_map(MetadataRevision::visuals)
.filter_map(|vis| image::load_from_memory(&vis.data).ok().map(|img| (vis.usage, img)))
// Replace this whole closure with MetadataRef::usage once we update
.flat_map(|meta| meta.visuals().iter().map(|v|(v.data.clone(), v.usage.clone())).collect::<Vec<_>>())
.filter_map(|(data, usage)| image::load_from_memory(&data).ok().map(|img| (usage, img)))
.max_by_key(|(usage, _)|
usage.map(|usage| match usage {
StandardVisualKey::FrontCover => 00,

View file

@ -9,6 +9,8 @@ mod file_select;
mod load_song;
mod peripheries;
mod editor;
mod player;
mod controls;
fn main() {
app::DelyriumApp::run(Settings::default()).unwrap();

257
src/player.rs Normal file
View file

@ -0,0 +1,257 @@
use std::time::Instant;
use core::time::Duration;
use rodio::Decoder;
use symphonia::core::formats::FormatReader;
use rodio::source::Buffered;
use rodio::OutputStream;
use rodio::Sink;
use rodio::Source;
use std::fmt;
pub type Song = Buffered<Decoder<std::io::Empty>>;
pub struct Player {
// [Buffered] is a pointer to a linked-list representation of the song, and so cloning
// it is as cheap as cloning an [Arc]. This should always point to the start of the
// song though, and so should not be changed after being initialized.
song: Song,
sink: Option<(Sink, OutputStream)>,
duration: Duration,
/// The position of the playhead when playback started or was last stopped
start_position: Duration,
/// The [`Instant`] playback started
playback_started: Option<Instant>,
}
impl Player {
/// Create a new player from a song
pub fn new(song: Box<dyn FormatReader>) -> Result<Self, PlayerError> {
let song = Decoder::new_from_format_reader(song)
.map_err(PlayerError::DecoderError)?
.buffered();
let duration = get_duration_of_song(song.clone());
let mut player = Player {
sink: None,
start_position: Duration::ZERO,
playback_started: None,
song, duration, };
player.get_or_set_sink()?;
Ok(player)
}
/// Check if an audio sink is available
///
/// Returns `true` if an output device is currently loaded, or `false` if a device was
/// not availble last time we tried to access one.
pub fn has_output_device(&self) -> bool {
self.sink.is_some()
}
/// Attempt to re-request access to the output sink
///
/// Returns `&self.sink`
///
/// Can produce a [PlayerError::PlayError]
fn try_set_sink(&mut self) -> Result<Option<&Sink>, PlayerError> {
self.sink = Some(OutputStream::try_default())
.and_then(|result|
if let Err(&rodio::StreamError::NoDevice) = result.as_ref() {
None // This is okay and doesn't need to raise an error
} else {
Some(result) // We'll report this error
})
.transpose()
.map_err(PlayerError::StreamError)?
.map(|(stream, handle)| Sink::try_new(&handle).map(|sink| (sink, stream)))
.transpose()
.map_err(PlayerError::PlayError)?;
Ok(self.sink.as_ref().map(|(s, _)| s))
}
/// Return the current active sink, or attempt to request a new one
///
/// Equivilent to calling [`try_set_sink()`] if the sink is missing
fn get_or_set_sink(&mut self) -> Result<Option<&Sink>, PlayerError> {
if self.sink.is_some() {
Ok(self.sink.as_ref().map(|(s, _)| s))
} else {
let song = self.song.clone();
let out = self.try_set_sink();
if let Ok(Some(sink)) = out.as_ref() {
sink.pause();
sink.append(song);
}
out
}
}
/// Attempt to resume playback, or start fresh
///
/// Returns `true` if playback was resumed, `false` if there is no audio sink
/// available to start play, and [PlayerError] if there was a problem requesting
/// access to the audio sink.
pub fn play(&mut self) -> Result<bool, PlayerError> {
if let Some(sink) = self.get_or_set_sink()? {
sink.play();
self.playback_started = Some(Instant::now());
Ok(true)
} else {
Ok(false)
}
}
/// Pause playback if playing, do nothing otherwise
///
/// May be resumed later with [`play()`]
pub fn pause(&mut self) {
if let Some((sink, _)) = &self.sink {
sink.pause();
self.start_position = self.position();
self.playback_started = None;
}
}
/// Returns true if the song is currently playing
///
/// That is, if playback has started, has not been paused, and has not ended naturally
/// yet.
pub fn is_playing(&self) -> bool {
self.playback_started.is_some() && self.position() < self.duration
}
/// Toggle playing
///
/// This calls [`play()`] is the track is currently paused or stopped, and calls
/// [`pause()`] is the track is currently playing. If the track was already playing,
/// this will always return `Ok(true)`.
pub fn toggle_play(&mut self) -> Result<bool, PlayerError> {
if self.position() == self.duration {
self.seek(Duration::ZERO)
.and_then(|device| if device { self.play() } else { Ok(false) } )
} else if self.playback_started.is_none() {
self.play()
} else {
self.pause();
Ok(true)
}
}
/// Return the duration of this track
///
/// This is cheap and exact. All processing is done before hand.
pub fn get_duration(&self) -> Duration {
self.duration
}
/// Attempt to seek to a given duration
///
/// This is pretty expensive, since due to technical limitations, this means resetting
/// the player to the beginning before skipping the given duration.
///
/// This can fail if there is no current output sink, and the attempt to access a
/// new one fails. If this is the case, false will be returned, unless the reason the
/// access failed was due to an error.
pub fn seek(&mut self, duration: Duration) -> Result<bool, PlayerError> {
let was_stopped = self.sink
.as_mut()
.map(|(s, _)| s.is_paused() || s.empty())
.unwrap_or(false);
let song = self.song.clone();
if let Some(sink) = self.try_set_sink()? {
sink.pause();
sink.append(
song.skip_duration(duration)
);
if was_stopped {
self.playback_started = None;
} else {
sink.play();
self.playback_started = Some(Instant::now());
}
self.start_position = duration;
Ok(true)
} else {
Ok(false)
}
}
/// Seek to a specific percentage (out of 1.0)
///
/// Performs a [`seek()`] operation, seeking to a given percentage of the song's full
/// length. See [`seek()`] for more details
pub fn seek_percentage(&mut self, percent: f32) -> Result<bool, PlayerError> {
self.seek(
self.duration.mul_f32(percent)
)
}
/// How far into the song the playback head currently is
///
/// Computes the duration of the song that is before the playback head. This is
/// really an approximation based on how much time has elapsed since playback started,
/// but it should be close enough for almost all purposes.
pub fn position(&self) -> Duration {
self.duration.min(
self.start_position +
self.playback_started.map_or(
Duration::ZERO,
|ts| Instant::now() - ts,
)
)
}
/// Computes the position as a fraction of the song duration
///
/// 0.0 represents the beginning of the song, while 1.0 represents the end. See
/// [`position()`] for more information.
pub fn position_percent(&self) -> f32 {
// nightly: self.position().div_duration_f32(self.duration)
self.position().as_secs_f32() / self.duration.as_secs_f32()
}
}
/// Manually calculate the exact length of a given song
///
/// This is really expensive, and involves decoding and inefficiently counting the number
/// of samples in the song, but produces an exact output. Use sparingly.
fn get_duration_of_song(song: Song) -> Duration {
let sample_rate = song.sample_rate() as u64;
let n_channels = song.channels() as u64;
let n_samples = song.count() as u64; // expensive!
let n_whole_seconds = n_samples / n_channels / sample_rate;
let remaining_samples = n_samples % sample_rate;
let n_nanos = remaining_samples * 1_000_000_000 / sample_rate;
Duration::new(n_whole_seconds, n_nanos as u32)
}
#[derive(Debug)]
pub enum PlayerError {
DecoderError(rodio::decoder::DecoderError),
PlayError(rodio::PlayError),
StreamError(rodio::StreamError),
}
impl fmt::Display for PlayerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::DecoderError(e) => write!(f, "Could not decode the provided song: {}", e),
Self::PlayError(e) => write!(f, "Problem playing to the audio output: {}", e),
Self::StreamError(e) => write!(f, "Problem connecting to the audio output: {}", e),
}
}
}
impl std::error::Error for PlayerError { }