├── .gitattributes
├── .github
└── workflows
│ └── deploy.yml
├── .gitignore
├── AirPlay.css
├── book.toml
├── ci
└── build.sh
├── css
├── examples.css
└── table.css
├── features.js
├── img
└── bullet.gif
├── index.html
├── js
├── .idea
│ ├── .gitignore
│ ├── .name
│ ├── ClojureProjectResolveSettings.xml
│ ├── compiler.xml
│ ├── encodings.xml
│ ├── libraries
│ │ ├── Leiningen__adzerk_boot_cljs_2_1_5.xml
│ │ ├── Leiningen__adzerk_boot_cljs_repl_0_4_0.xml
│ │ ├── Leiningen__adzerk_boot_reload_0_6_0.xml
│ │ ├── Leiningen__args4j_2_0_26.xml
│ │ ├── Leiningen__bendyorke_boot_postcss_0_0_1.xml
│ │ ├── Leiningen__bidi_2_1_6.xml
│ │ ├── Leiningen__binaryage_devtools_0_9_11.xml
│ │ ├── Leiningen__binaryage_env_config_0_2_2.xml
│ │ ├── Leiningen__boot_deps_0_1_9.xml
│ │ ├── Leiningen__camel_snake_kebab_0_4_1.xml
│ │ ├── Leiningen__cheshire_5_7_1.xml
│ │ ├── Leiningen__cider_piggieback_0_3_9.xml
│ │ ├── Leiningen__cljs_ajax_0_8_0.xml
│ │ ├── Leiningen__cljsjs_boot_cljsjs_0_10_4.xml
│ │ ├── Leiningen__cljsjs_classnames_2_2_5_1.xml
│ │ ├── Leiningen__cljsjs_create_react_class_15_6_3_1.xml
│ │ ├── Leiningen__cljsjs_date_fns_1_29_0_0.xml
│ │ ├── Leiningen__cljsjs_marked_0_3_5_1.xml
│ │ ├── Leiningen__cljsjs_moment_2_22_0_0.xml
│ │ ├── Leiningen__cljsjs_popperjs_1_14_3_1.xml
│ │ ├── Leiningen__cljsjs_prop_types_15_6_1_0.xml
│ │ ├── Leiningen__cljsjs_react_16_9_0_1.xml
│ │ ├── Leiningen__cljsjs_react_bootstrap_1_0_0_beta_14_0.xml
│ │ ├── Leiningen__cljsjs_react_datepicker_2_3_0_0.xml
│ │ ├── Leiningen__cljsjs_react_dom_16_9_0_1.xml
│ │ ├── Leiningen__cljsjs_react_dom_server_16_9_0_1.xml
│ │ ├── Leiningen__cljsjs_react_onclickoutside_6_7_1_1.xml
│ │ ├── Leiningen__cljsjs_react_popper_1_0_2_0.xml
│ │ ├── Leiningen__clojure_complete_0_2_5.xml
│ │ ├── Leiningen__com_cognitect_transit_clj_0_8_309.xml
│ │ ├── Leiningen__com_cognitect_transit_cljs_0_8_256.xml
│ │ ├── Leiningen__com_cognitect_transit_java_0_8_332.xml
│ │ ├── Leiningen__com_cognitect_transit_js_0_8_846.xml
│ │ ├── Leiningen__com_fasterxml_jackson_core_jackson_core_2_8_6.xml
│ │ ├── Leiningen__com_fasterxml_jackson_dataformat_jackson_dataformat_cbor_2_8_6.xml
│ │ ├── Leiningen__com_fasterxml_jackson_dataformat_jackson_dataformat_smile_2_8_6.xml
│ │ ├── Leiningen__com_google_code_findbugs_jsr305_3_0_1.xml
│ │ ├── Leiningen__com_google_code_gson_gson_2_7.xml
│ │ ├── Leiningen__com_google_errorprone_error_prone_annotations_2_0_18.xml
│ │ ├── Leiningen__com_google_guava_guava_25_1_jre.xml
│ │ ├── Leiningen__com_google_j2objc_j2objc_annotations_1_1.xml
│ │ ├── Leiningen__com_google_javascript_closure_compiler_externs_v20180805.xml
│ │ ├── Leiningen__com_google_javascript_closure_compiler_unshaded_v20180805.xml
│ │ ├── Leiningen__com_google_jsinterop_jsinterop_annotations_1_0_0.xml
│ │ ├── Leiningen__com_google_protobuf_protobuf_java_3_0_2.xml
│ │ ├── Leiningen__com_googlecode_json_simple_json_simple_1_1_1.xml
│ │ ├── Leiningen__commons_codec_1_10.xml
│ │ ├── Leiningen__commons_logging_1_2.xml
│ │ ├── Leiningen__compassus_1_0_0_alpha3.xml
│ │ ├── Leiningen__devcards_0_2_6.xml
│ │ ├── Leiningen__griff_boot2nix_1_1_0.xml
│ │ ├── Leiningen__http_kit_2_1_18.xml
│ │ ├── Leiningen__kibu_pushy_0_3_8.xml
│ │ ├── Leiningen__net_cgrand_macrovich_0_2_1.xml
│ │ ├── Leiningen__nrepl_0_4_5.xml
│ │ ├── Leiningen__nrepl_bencode_1_0_0.xml
│ │ ├── Leiningen__onetom_boot_lein_generate_0_1_3.xml
│ │ ├── Leiningen__org_apache_httpcomponents_httpasyncclient_4_1_3.xml
│ │ ├── Leiningen__org_apache_httpcomponents_httpclient_4_5_3.xml
│ │ ├── Leiningen__org_apache_httpcomponents_httpcore_4_4_6.xml
│ │ ├── Leiningen__org_apache_httpcomponents_httpcore_nio_4_4_6.xml
│ │ ├── Leiningen__org_checkerframework_checker_qual_2_0_0.xml
│ │ ├── Leiningen__org_clojure_clojure_1_10_1.xml
│ │ ├── Leiningen__org_clojure_clojurescript_1_10_597.xml
│ │ ├── Leiningen__org_clojure_core_async_0_6_532.xml
│ │ ├── Leiningen__org_clojure_core_cache_0_8_2.xml
│ │ ├── Leiningen__org_clojure_core_memoize_0_8_2.xml
│ │ ├── Leiningen__org_clojure_core_specs_alpha_0_2_44.xml
│ │ ├── Leiningen__org_clojure_data_codec_0_1_0.xml
│ │ ├── Leiningen__org_clojure_data_json_0_2_6.xml
│ │ ├── Leiningen__org_clojure_data_priority_map_0_0_7.xml
│ │ ├── Leiningen__org_clojure_data_xml_0_2_0_alpha6.xml
│ │ ├── Leiningen__org_clojure_google_closure_library_0_0_20170809_b9c14c6b.xml
│ │ ├── Leiningen__org_clojure_google_closure_library_third_party_0_0_20170809_b9c14c6b.xml
│ │ ├── Leiningen__org_clojure_spec_alpha_0_2_176.xml
│ │ ├── Leiningen__org_clojure_tools_analyzer_0_7_0.xml
│ │ ├── Leiningen__org_clojure_tools_analyzer_jvm_0_7_3.xml
│ │ ├── Leiningen__org_clojure_tools_logging_0_4_1.xml
│ │ ├── Leiningen__org_clojure_tools_reader_1_3_2.xml
│ │ ├── Leiningen__org_codehaus_mojo_animal_sniffer_annotations_1_14.xml
│ │ ├── Leiningen__org_javassist_javassist_3_18_1_GA.xml
│ │ ├── Leiningen__org_mozilla_rhino_1_7R5.xml
│ │ ├── Leiningen__org_msgpack_msgpack_0_6_12.xml
│ │ ├── Leiningen__org_omcljs_om_1_0_0_beta2.xml
│ │ ├── Leiningen__org_ow2_asm_asm_5_2.xml
│ │ ├── Leiningen__pandeiro_boot_http_0_8_3.xml
│ │ ├── Leiningen__prismatic_dommy_1_1_0.xml
│ │ ├── Leiningen__prismatic_schema_1_1_7.xml
│ │ ├── Leiningen__re_frame_0_11_0_rc3.xml
│ │ ├── Leiningen__reagent_0_9_0_rc4.xml
│ │ ├── Leiningen__sablono_0_8_6.xml
│ │ ├── Leiningen__tigris_0_1_1.xml
│ │ └── Leiningen__weasel_0_7_0.xml
│ ├── misc.xml
│ ├── modules.xml
│ └── vcs.xml
├── airplay-spec-js.iml
├── boot.properties
├── build.boot
├── html
│ ├── cards.html
│ └── index.html
├── libs
│ └── .git-keep
├── project.clj
├── resources
│ ├── app-repl.cljs.edn
│ ├── app.cljs.edn
│ ├── cards-repl.cljs.edn
│ └── cards.cljs.edn
└── src
│ ├── airplay
│ ├── bits.cljs
│ ├── examples.cljs
│ ├── examples
│ │ └── data.cljs
│ ├── features.cljs
│ ├── main.cljs
│ ├── math.cljs
│ ├── plist.cljs
│ ├── state.cljs
│ └── ui
│ │ ├── airplay_service.cljs
│ │ ├── core.cljs
│ │ ├── examples.cljs
│ │ ├── features.cljs
│ │ └── flags.cljs
│ └── cards
│ └── ui.cljs
├── long.js
├── nix
├── sources.json
└── sources.nix
├── shell.nix
├── src
├── SUMMARY.md
├── audio
│ ├── README.md
│ ├── airport_express_authentication.md
│ ├── info.md
│ ├── metadata.md
│ ├── remote_control.md
│ ├── rtp_streams.md
│ ├── rtsp_requests
│ │ ├── README.md
│ │ ├── announce.md
│ │ ├── flush.md
│ │ ├── flushbuffered.md
│ │ ├── get_info.md
│ │ ├── options.md
│ │ ├── post_audio_mode.md
│ │ ├── post_auth_setup.md
│ │ ├── post_command.md
│ │ ├── post_feedback.md
│ │ ├── post_fp_setup.md
│ │ ├── post_pair_setup.md
│ │ ├── post_pair_verify.md
│ │ ├── record.md
│ │ ├── setpeers.md
│ │ ├── setup.md
│ │ └── teardown.md
│ └── volume_control.md
├── contributing.md
├── data
│ ├── RTSP-get-info-req.bin
│ └── examples
│ │ ├── AirServer
│ │ ├── info.plist
│ │ ├── info.plist.base64
│ │ └── info.plist.xml
│ │ ├── AppleTV-4K
│ │ ├── info.plist
│ │ ├── info.plist.base64
│ │ └── info.plist.xml
│ │ └── Denon-AVR-X3500H
│ │ ├── info.plist
│ │ ├── info.plist.base64
│ │ └── info.plist.xml
├── features.md
├── history.md
├── introduction.md
├── known_implementations.md
├── pairing
│ ├── README.md
│ ├── hkp.md
│ └── legacy.md
├── photos
│ ├── README.md
│ ├── events.md
│ ├── http_requests.md
│ ├── photo_caching.md
│ └── slideshows.md
├── resources.md
├── screen_mirroring
│ ├── README.md
│ ├── http_requests.md
│ ├── stream_packets.md
│ └── time_synchronization.md
├── service_discovery.md
├── status_flags.md
└── video
│ ├── README.md
│ ├── events.md
│ └── http_requests.md
└── theme
├── book.js
├── css
├── chrome.css
├── general.css
├── print.css
└── variables.css
├── favicon.png
├── highlight.css
├── highlight.js
└── index.hbs
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.bin binary
2 |
3 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | jobs:
8 | pages:
9 | name: GitHub Pages
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@master
13 | - uses: cachix/install-nix-action@v6
14 | - name: Install Rust (rustup)
15 | run: rustup update stable --no-self-update && rustup default stable
16 | - name: Build book
17 | run: nix-shell --run ci/build.sh
18 | - name: Deploy to GitHub
19 | env:
20 | GITHUB_DEPLOY_KEY: ${{ secrets.GITHUB_DEPLOY_KEY }}
21 | run: |
22 | touch book/.nojekyll
23 | curl -LsSf https://raw.githubusercontent.com/rust-lang/simpleinfra/master/setup-deploy-keys/src/deploy.rs | nix-shell --run 'rustc - -o /tmp/deploy'
24 | cd book
25 | /tmp/deploy
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | book
2 | /js/release
3 | /js/target
4 | /js/.nrepl-port
5 |
--------------------------------------------------------------------------------
/AirPlay.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Segoe UI", "Lucida Grande", "Trebuchet MS", Arial, "lucida sans unicode", sans-serif;
3 | color: #333;
4 | background: #ccc none;
5 | padding: 0px 30px;
6 | }
7 |
8 | a {
9 | color: #686868;
10 | }
11 |
12 | ul.toc {
13 | margin-left: 4em;
14 | margin-right: 0px;
15 | padding-left: 0px;
16 | margin-bottom: 0px;
17 | }
18 |
19 | ul.toc ul {
20 | margin-left: 1em;
21 | margin-right: 0px;
22 | padding-left: 0px;
23 | margin-bottom: 0px;
24 | }
25 |
26 | ul.toc li {
27 | background: none;
28 | padding-left: 0px;
29 | list-style: none;
30 | }
31 |
32 | ul.toc li a {
33 | text-decoration: none;
34 | }
35 |
36 | a:hover {
37 | color: #09598a;
38 | }
39 |
40 | h1 {
41 | color: #09598a;
42 | margin-top: 50px;
43 | margin-bottom: 20px;
44 | border-top: 2px solid #09598a;
45 | border-bottom: 2px solid #09598a;
46 | padding: 10px 20px;
47 | background-color: #eff8fd;
48 | }
49 |
50 | h1.title {
51 | color: #424242;
52 | text-shadow: 0px 1px 3px #888;
53 | text-align: center;
54 | margin-top: 0px;
55 | margin-bottom: 20px;
56 | padding-bottom: 10px;
57 | border-bottom: 4px solid #09598a;
58 | border-top: 4px solid #09598a;
59 | }
60 |
61 | h2 {
62 | color: #09598a;
63 | margin-top: 25px;
64 | margin-bottom: 15px;
65 | border-bottom: 1px solid #09598a;
66 | }
67 |
68 | h3 {
69 | color: #09598A;
70 | margin-top: 10px;
71 | }
72 |
73 | em {
74 | color: #09598a;
75 | font-style: normal;
76 | font-weight: bold;
77 | }
78 |
79 | p {
80 | margin-top: 3px;
81 | margin-bottom: 15px;
82 | text-align: justify;
83 | }
84 |
85 | code {
86 | font-weight: bold;
87 | }
88 |
89 | pre {
90 | background-color: #eff8fd;
91 | border: 1px dashed #aaa;
92 | border-top: none;
93 | padding: 5px;
94 | margin: 0px 10px 30px 30px;
95 | width: 650px;
96 | overflow: auto;
97 | font-weight: bold;
98 | }
99 |
100 | p.caption {
101 | background-color: #ccccff;
102 | border: 1px dashed #aaa;
103 | border-bottom: none;
104 | padding: 5px;
105 | margin: 10px 10px 0px 30px;
106 | width: 650px;
107 | overflow: auto;
108 | font-size: 80%;
109 | font-variant: small-caps;
110 | font-weight: bold;
111 | }
112 |
113 | p.cli_srv {
114 | background-color: #ffcccc;
115 | border: 1px dashed #aaa;
116 | border-bottom: none;
117 | padding: 5px;
118 | margin: 10px 10px 0px 30px;
119 | width: 650px;
120 | overflow: auto;
121 | font-size: 80%;
122 | font-variant: small-caps;
123 | font-weight: bold;
124 | }
125 |
126 | p.srv_cli {
127 | background-color: #ccffcc;
128 | border: 1px dashed #aaa;
129 | border-bottom: none;
130 | padding: 5px;
131 | margin: 10px 10px 0px 30px;
132 | width: 650px;
133 | overflow: auto;
134 | font-size: 80%;
135 | font-variant: small-caps;
136 | font-weight: bold;
137 | }
138 |
139 | .ex {
140 | text-decoration: underline;
141 | font-weight: bold;
142 | }
143 |
144 | ul {
145 | margin-left: 1em;
146 | margin-right: 2em;
147 | padding-left: 0.2em;
148 | margin-bottom: 1em;
149 | }
150 |
151 | ul li {
152 | background: url(img/bullet.gif) 0em 0.5em no-repeat;
153 | padding-left: 0.8em;
154 | list-style: none;
155 | }
156 |
157 | table {
158 | border-collapse: separate;
159 | margin: 10px 10px 30px 30px;
160 | width: 650px;
161 | }
162 |
163 | table th {
164 | color: #4381a7;
165 | font-variant: small-caps;
166 | font-size: 12pt;
167 | height: 30px;
168 | background: #c8e3f9;
169 | }
170 |
171 | table td, table th {
172 | padding: 5px 5px;
173 | text-align: left;
174 | }
175 |
176 | table tr {
177 | background-color: #f1f8fd;
178 | }
179 |
180 | table tr:hover {
181 | background-color: #e4f2fc;
182 | }
183 |
184 | table tr.selected {
185 | background-color: #75bdf0;
186 | }
187 |
188 | table td {
189 | font-size: 9pt;
190 | }
191 |
192 | #content {
193 | padding: 20px;
194 | border: 1px solid #999;
195 | background: #fff none;
196 | color: #000;
197 | width: 800px;
198 | margin: auto;
199 | }
200 |
201 | #footer {
202 | font-size: 80%;
203 | border-top: 3px solid #09598a;
204 | padding-top: 10px;
205 | }
206 |
207 | .noscript {
208 | display: none;
209 | }
210 |
--------------------------------------------------------------------------------
/book.toml:
--------------------------------------------------------------------------------
1 | [book]
2 | authors = ["Brian Olsen"]
3 | language = "en"
4 | multilingual = false
5 | src = "src"
6 | title = "Unofficial AirPlay Specification"
7 |
8 | [output.html]
9 | additional-js = []
10 | additional-css = ["css/table.css", "css/examples.css"]
11 |
--------------------------------------------------------------------------------
/ci/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | cd js
4 | boot release
5 | cd -
6 | mdbook build
7 | mkdir book/js
8 | cp js/release/app.js* book/js
9 |
--------------------------------------------------------------------------------
/css/examples.css:
--------------------------------------------------------------------------------
1 | .client_server, .server_client {
2 | border: 1px dashed #aaa;
3 | margin: 10px 10px 0px 30px;
4 | }
5 | .client_server p, .server_client p {
6 | margin: 0;
7 | padding: 5px;
8 | overflow: auto;
9 | font-size: 80%;
10 | font-variant: small-caps;
11 | font-weight: bold;
12 | }
13 | .client_server p {
14 | background-color: #ffcccc;
15 | }
16 |
17 | .server_client p {
18 | background-color: #ccffcc;
19 | }
20 | .client_server pre, .server_client pre {
21 | margin-top: 0;
22 | margin-bottom: 0;
23 | }
24 |
25 | .ex {
26 | text-decoration: underline;
27 | font-weight: bold;
28 | }
29 |
--------------------------------------------------------------------------------
/css/table.css:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | table {
4 | border-collapse: separate;
5 | margin: 10px 10px 30px 30px;
6 | width: 650px;
7 | }
8 | */
9 |
10 | table thead, table tbody.header {
11 | background: var(--table-header-bg);
12 | }
13 | table thead td, table tbody.header td {
14 | font-weight: 700;
15 | border: none;
16 | }
17 | table thead th, table tbody.header th {
18 | padding: 3px 20px;
19 | }
20 | table thead tr, table tbody.header tr {
21 | border: 1px var(--table-header-bg) solid;
22 | }
23 |
24 | table th {
25 | color: #4381a7;
26 | font-variant: small-caps;
27 | font-size: 12pt;
28 | height: 30px;
29 | /*background: #c8e3f9;*/
30 | }
31 |
32 | table td, table th {
33 | /*padding: 5px 5px;*/
34 | text-align: left;
35 | }
36 |
37 | /*
38 | table tbody.features-body tr {
39 | background-color: #f1f8fd;
40 | }
41 | */
42 |
43 | table.bits tbody.rows tr:hover {
44 | background-color: #e4f2fc;
45 | }
46 |
47 | table.bits tbody tr.selected {
48 | background-color: #75bdf0;
49 | }
50 |
51 | table td {
52 | font-size: 9pt;
53 | }
54 |
--------------------------------------------------------------------------------
/features.js:
--------------------------------------------------------------------------------
1 | function toLong(s) {
2 | var ret = s.trim();
3 | try {
4 | if (ret.startsWith("0x")) {
5 | return Long.fromString(ret.substring(2), 16);
6 | }
7 | return Long.fromString(ret, 10);
8 | } catch (e) {
9 | return Long.ZERO;
10 | }
11 | }
12 | function setBitClassName(prefix, value, selected, notSelected) {
13 | for (var i=0; i<65; i++) {
14 | var b = Long.ONE.shiftLeft(i);
15 | var el = document.getElementById(prefix + "-bit" + i);
16 | if (el) {
17 | if (value.and(b).equals(b)) {
18 | el.className = selected;
19 | } else {
20 | el.className = notSelected;
21 | }
22 | }
23 | }
24 | }
25 | function updateFlagTable(prefix, value) {
26 | if (value.equals(Long.ZERO)) {
27 | document.getElementById(prefix + "-output").className = "noscript";
28 | setBitClassName(prefix, Long.ZERO, "", "")
29 | document.getElementById(prefix).className = "";
30 | } else {
31 | document.getElementById(prefix + "-output").className = "";
32 | setBitClassName(prefix, value, "selected", "notSelected")
33 | document.getElementById(prefix).className = "selecting";
34 | }
35 |
36 | document.getElementById(prefix + "-dec").innerHTML = value.toString(10);
37 | document.getElementById(prefix + "-hex").innerHTML = "0x" + value.toString(16);
38 | }
39 |
40 | function exampleChanged(prefix, fn) {
41 | var el = document.getElementById(prefix + "-examples");
42 | document.getElementById(prefix + "-input").value = el.value;
43 | fn(el);
44 | }
45 |
46 | function inputChanged(prefix, fn, bit) {
47 | var el = document.getElementById(prefix + "-input");
48 | document.getElementById(prefix + "-examples").value = "";
49 | document.getElementById(prefix + "-interactive").className = "";
50 | fn(el, bit);
51 | }
52 |
53 | function flipBit(prefix, fn, bit) {
54 | var el = document.getElementById(prefix + "-input");
55 | document.getElementById(prefix + "-examples").value = "";
56 | document.getElementById(prefix + "-interactive").className = "";
57 | fn(el, bit)
58 | }
59 |
60 | function updateFeatures(el, bit) {
61 | var featuresS = el.value;
62 | var comma = featuresS.indexOf(",");
63 | var features = 0;
64 | if (comma >= 0) {
65 | var high = toLong(featuresS.substring(comma + 1));
66 | var low = toLong(featuresS.substring(0, comma));
67 | features = high.shiftLeft(32).or(low);
68 | } else {
69 | features = toLong(featuresS);
70 | }
71 | if (typeof bit != "undefined") {
72 | var b = Long.ONE.shiftLeft(bit);
73 | features = features.xor(b);
74 | el.value = "0x" + features.toString(16);
75 | }
76 | updateFlagTable("airplay-features", features);
77 | var txt = "0x" + features.and(Long.fromBits(0xffffffff, 0, true)).toString(16) +
78 | ",0x" + features.shiftRight(32).toString(16);
79 | document.getElementById("airplay-features-txt").innerHTML = txt;
80 | }
81 |
82 | function updateSystemFlags(el, bit) {
83 | var systemFlagsS = el.value;
84 | var systemFlags = 0;
85 | systemFlags = toLong(systemFlagsS);
86 | if (typeof bit != "undefined") {
87 | var b = Long.ONE.shiftLeft(bit);
88 | systemFlags = systemFlags.xor(b);
89 | el.value = "0x" + systemFlags.toString(16);
90 | }
91 | updateFlagTable("airplay-system-flags", systemFlags);
92 | }
93 |
94 | function installHandlers(prefix, fn) {
95 | var input = function() {
96 | inputChanged(prefix, fn);
97 | }
98 | var example = function() {
99 | exampleChanged(prefix, fn);
100 | }
101 | var changeBit = function(ev) {
102 | var target = ev.target;
103 | while (target != null && target.tagName != "TR") {
104 | target = target.parentElement;
105 | }
106 | if (target == null) {
107 | return;
108 | }
109 | var bit = parseInt(target.id.substring(prefix.length + "-bit".length));
110 | inputChanged(prefix, fn, bit);
111 | }
112 | var el = document.getElementById(prefix + "-input");
113 | el.onkeyup = input;
114 | el.onpaste = input;
115 | var el = document.getElementById(prefix + "-examples");
116 | el.onchange = example;
117 | var el = document.getElementById(prefix + "-bit0");
118 | el.parentNode.onclick = changeBit;
119 | input();
120 | }
121 | installHandlers("airplay-features", updateFeatures)
122 | installHandlers("airplay-system-flags", updateSystemFlags)
123 |
--------------------------------------------------------------------------------
/img/bullet.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openairplay/airplay-spec/00063da0e7f16c20fbfb5d29331334da1a89590a/img/bullet.gif
--------------------------------------------------------------------------------
/js/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /workspace.xml
--------------------------------------------------------------------------------
/js/.idea/.name:
--------------------------------------------------------------------------------
1 | airplay-spec
--------------------------------------------------------------------------------
/js/.idea/ClojureProjectResolveSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
bit | name | description |
---|---|---|
0 | Test | Testing |
1 | Muh | more |
<?xml version="1.0" encoding="UTF-8"?>
28 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
29 | <plist version="1.0">
30 | <dict>
31 | <key>PTPInfo</key>
32 | <string>OpenAVNU ArtAndLogic-aPTP-changes 1.0</string>
33 | <key>build</key>
34 | <string>17.0</string>
35 | <key>deviceID</key>
36 | <string>00:05:CD:D4:42:96</string>
37 | <key>features</key>
38 | <integer>496155769145856</integer>
39 | <key>firmwareBuildDate</key>
40 | <string>Jan 30 2019</string>
41 | <key>firmwareRevision</key>
42 | <string>1.505.130</string>
43 | <key>keepAliveLowPower</key>
44 | <true/>
45 | <key>keepAliveSendStatsAsBody</key>
46 | <true/>
47 | <key>manufacturer</key>
48 | <string>Sound United</string>
49 | <key>model</key>
50 | <string>AVR-X3500H</string>
51 | <key>name</key>
52 | <string>Daemon</string>
53 | <key>nameIsFactoryDefault</key>
54 | <false/>
55 | <key>pi</key>
56 | <string>650c20ee-842c-4c01-80ed-22b7fb239881</string>
57 | <key>protocolVersion</key>
58 | <string>1.1</string>
59 | <key>sdk</key>
60 | <string>AirPlay;2.0.2</string>
61 | <key>sourceVersion</key>
62 | <string>366.0</string>
63 | <key>statusFlags</key>
64 | <integer>4</integer>
65 | <key>txtAirPlay</key>
66 | <data>
67 | BWFjbD0wGmRldmljZWlkPTAwOjA1OkNEOkQ0OjQyOjk2G2ZlYXR1cmVzPTB4NDQ1RjhB
68 | MDAsMHgxQzM0MAdyc2Y9MHgwEGZ2PXAyMC4xLjUwNS4xMzAJZmxhZ3M9MHg0EG1vZGVs
69 | PUFWUi1YMzUwMEgZbWFudWZhY3R1cmVyPVNvdW5kIFVuaXRlZBtzZXJpYWxOdW1iZXI9
70 | QkJXMzYxODEyMTIzNjQNcHJvdG92ZXJzPTEuMQ1zcmN2ZXJzPTM2Ni4wJ3BpPTY1MGMy
71 | MGVlLTg0MmMtNGMwMS04MGVkLTIyYjdmYjIzOTg4MShnaWQ9NjUwYzIwZWUtODQyYy00
72 | YzAxLTgwZWQtMjJiN2ZiMjM5ODgxBmdjZ2w9MENwaz0wYWNkN2Q2MWIyODRjMGFmNzFj
73 | N2VmNGY3ZWE2NDRkZGRlYzIzOGVmMjdjM2MwYWQzZDVkM2JiOWM4YjMxZThm
74 | </data>
75 | </dict>
76 | </plist>
77 |
78 |
79 |
87 |
88 |
--------------------------------------------------------------------------------
/js/libs/.git-keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openairplay/airplay-spec/00063da0e7f16c20fbfb5d29331334da1a89590a/js/libs/.git-keep
--------------------------------------------------------------------------------
/js/project.clj:
--------------------------------------------------------------------------------
1 | (defproject
2 | griff/airplay-spec-js
3 | "0.1.0-SNAPSHOT"
4 | :repositories
5 | [["clojars" {:url "https://repo.clojars.org/"}]
6 | ["maven-central" {:url "https://repo1.maven.org/maven2"}]]
7 | :dependencies
8 | [[adzerk/boot-cljs "2.1.5"]
9 | [adzerk/boot-cljs-repl "0.4.0"]
10 | [boot-deps "0.1.9"]
11 | [bendyorke/boot-postcss "0.0.1" :scope "test"]
12 | [adzerk/boot-reload "0.6.0" :scope "test"]
13 | [pandeiro/boot-http "0.8.3" :scope "test"]
14 | [cider/piggieback "0.3.9" :scope "test"]
15 | [weasel "0.7.0" :scope "test"]
16 | [nrepl "0.4.5" :scope "test"]
17 | [cljsjs/boot-cljsjs "0.10.4" :scope "test"]
18 | [griff/boot2nix "1.1.0" :scope "test"]
19 | [org.clojure/clojure "1.10.1"]
20 | [onetom/boot-lein-generate "0.1.3" :scope "test"]
21 | [org.clojure/clojurescript "1.10.597"]
22 | [sablono "0.8.6"]
23 | [prismatic/dommy "1.1.0"]
24 | [reagent "0.9.0-rc4"]
25 | [re-frame "0.11.0-rc3"]
26 | [cljs-ajax "0.8.0"]
27 | [compassus "1.0.0-alpha3"]
28 | [bidi "2.1.6"]
29 | [kibu/pushy "0.3.8"]
30 | [org.clojure/core.async "0.6.532"]
31 | [camel-snake-kebab "0.4.1"]
32 | [cljsjs/react-bootstrap "1.0.0-beta.14-0"]
33 | [cljsjs/react-datepicker "2.3.0-0"]
34 | [org.clojure/data.xml "0.2.0-alpha6"]
35 | [devcards "0.2.6"]
36 | [binaryage/devtools "0.9.11"]]
37 | :source-paths
38 | ["src"]
39 | :resource-paths
40 | ["libs" "html" "resources"])
--------------------------------------------------------------------------------
/js/resources/app-repl.cljs.edn:
--------------------------------------------------------------------------------
1 | {:compiler-options {:language-in :ecmascript5
2 | :asset-path "http://localhost:3002/app-repl.out"}}
3 |
--------------------------------------------------------------------------------
/js/resources/app.cljs.edn:
--------------------------------------------------------------------------------
1 | {:require [airplay.main]
2 | :compiler-options {:language-in :ecmascript5
3 | :static-fns true
4 | :asset-path "http://localhost:3002/app.out"}}
5 |
--------------------------------------------------------------------------------
/js/resources/cards-repl.cljs.edn:
--------------------------------------------------------------------------------
1 | {:require [cards.ui]
2 | :init-fns [cards.ui/start]
3 | :compiler-options {:language-in :ecmascript5}}
4 |
--------------------------------------------------------------------------------
/js/resources/cards.cljs.edn:
--------------------------------------------------------------------------------
1 | {:require [cards.ui]
2 | :init-fns [cards.ui/start]
3 | :compiler-options {:static-fns true}}
4 |
--------------------------------------------------------------------------------
/js/src/airplay/bits.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.bits
2 | (:require [dommy.core :as dc :refer-macros [sel sel1]]
3 | [airplay.math :as am]
4 | [dommy.utils :as du]))
5 |
6 | (defn table->map [table]
7 | (let [names (rest (map dc/text (sel table [:thead :th])))
8 | bits (->> (sel table [:tbody "td:first-child"])
9 | (map dc/text)
10 | (map keyword))]
11 | (->> (sel table [:tbody :tr])
12 | (map #(->> (sel %1 :td)
13 | rest
14 | (map dc/text)
15 | (zipmap names)))
16 | (zipmap bits)
17 | (into (sorted-map)))))
18 |
19 | (defn bits-table->map [table]
20 | (let [names (rest (map dc/text (sel table [:thead :th])))
21 | bits (->> (sel table [:tbody "td:first-child"])
22 | (map dc/text)
23 | (map am/parse-int))]
24 | (->> (sel table [:tbody :tr])
25 | (map #(->> (sel %1 :td)
26 | rest
27 | (map dc/text)
28 | (zipmap names)))
29 | (zipmap bits)
30 | (into (sorted-map)))))
31 |
32 | (defn find-table* [el]
33 | (if (= "TABLE" (.-tagName el))
34 | el
35 | (sel1 el :table)))
36 |
37 | (defn first-table [header]
38 | (if-let [f header]
39 | (let [h (dc/parent f)]
40 | (->> h
41 | dc/parent
42 | dc/children
43 | du/->Array
44 | (drop-while #(not (= % h)))
45 | (map find-table*)
46 | (filter some?)
47 | first))))
48 |
49 | (comment
50 | (if-let [f (sel1 "#features")]
51 | (let [h (dc/parent f)]
52 | (->> h
53 | dc/parent
54 | dc/children
55 | du/->Array
56 | (drop-while #(not (= % h)))
57 | (map find-table*)
58 | (filter some?)
59 | first)))
60 | (first-table (sel1 "#features")))
--------------------------------------------------------------------------------
/js/src/airplay/examples.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.examples
2 | (:require [clojure.string :as s]))
3 |
4 |
--------------------------------------------------------------------------------
/js/src/airplay/features.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.features
2 | (:require [clojure.string :as cs]
3 | [airplay.math :as am]))
4 |
5 | (defn features->mdns [value]
6 | (let [low (am/bit-and value (am/bits->long 0xffffffff 0))
7 | high (am/bit-shift-right value 32)]
8 | (str (am/->hex low) "," (am/->hex high))))
9 |
10 | (defn str->features
11 | "Convert all possible ways to specify features into a long"
12 | [s]
13 | (let [[low high] (map cs/trim (cs/split s #"," 2))]
14 | (if high
15 | (am/bit-or (am/try-str->long low) (am/bit-shift-left (am/try-str->long high) 32))
16 | (am/try-str->long low))))
17 |
--------------------------------------------------------------------------------
/js/src/airplay/main.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.main
2 | (:require [clojure.string :as cs]
3 | [dommy.core :as dc :refer-macros [sel sel1]]
4 | [reagent.core :as rc]
5 | [react :as react]
6 | [re-frame.core :as rf]
7 | [airplay.math :as am]
8 | [airplay.plist :as plist]
9 | [airplay.ui.features :as auf]
10 | [airplay.ui.examples :as aue]
11 | [airplay.ui.flags :as aus]
12 | [airplay.ui.airplay-service :as auas]
13 | [clojure.data.xml :as xml]
14 | [clojure.string :as s]
15 | [goog.crypt.base64 :as base64]))
16 |
17 | #_(reset! thonix/app (core/mount @thonix/app app/Root "app"))
18 |
19 | (js/console.log "UI")
20 |
21 |
22 | (comment
23 | (-> (sel1 "#info code")
24 | dc/text)
25 | (-> (sel1 "#info code")
26 | dc/text
27 | plist/parse)
28 | (let [txt (-> (sel1 "#info code")
29 | dc/text)]
30 | (= (-> txt
31 | plist/parse
32 | plist/emit-pretty-str)
33 | txt))
34 | (first (filter xml/element? (:content (xml/parse-str (dc/text (sel1 "#info code"))))))
35 | (bit-set)
36 | (dc/closest) (map dc/text (sel [:table "td:first-child"])) :#testing
37 | (dc/selector [:thead :th])
38 | (->> (sel (sel1 :table) [:tbody :tr])
39 | (map #(sel1 %1 :td))
40 | (map dc/text)
41 | )
42 | (map #(sel1 %1 :td) (sel (sel1 :table) [:tbody :tr]))
43 | (map dc/text (sel (sel1 :table) [:thead :th]))
44 | (let [table (sel1 :table)]
45 | (if (not (dc/closest table :#features-list))
46 | (bits-table->map table)))
47 | (bits-table->map (sel1 :table))
48 | features
49 | (or (sel1 :#features-list) (sel1 :table)))
50 |
51 | (defn ^:export main []
52 | (js/console.log "Main")
53 | (rf/dispatch-sync [:initialize])
54 | (auf/init)
55 | (aue/init)
56 | (aus/init)
57 | #_(auas/init))
58 | (main)
59 |
60 | (defn reload []
61 | (rf/clear-subscription-cache!)
62 | #_(rc/force-update-all))
63 |
--------------------------------------------------------------------------------
/js/src/airplay/math.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.math
2 | (:refer-clojure :exclude [long bit-and bit-or bit-xor bit-not bit-shift-left bit-shift-right bit-test bit-set bit-flip])
3 | (:import goog.math.Long))
4 |
5 | (extend-type Long
6 | IEquiv
7 | (-equiv [o other] (.equals o other)))
8 |
9 | (defn parse-int
10 | ([s] (js/parseInt s))
11 | ([s radix] (js/parseInt s radix)))
12 |
13 | (defn parse-float [s]
14 | (js/parseFloat s))
15 |
16 | (defn long [x]
17 | (if (instance? Long x)
18 | x
19 | (condp = x
20 | 0 (.getZero Long)
21 | 1 (.getOne Long)
22 | -1 (.getNegOne Long)
23 | (.fromNumber Long x))))
24 |
25 | (defn ->str
26 | "Convert long to string"
27 | ([v] (->str v 10))
28 | ([v radix] (.toString (long v) radix)))
29 |
30 | (defn ->hex [value]
31 | (str "0x" (.toUpperCase (->str value 16))))
32 |
33 | (defn bit-and
34 | "Bitwise and"
35 | ([x y] (.and (long x) (long y)))
36 | ([x y & more] (reduce bit-and (bit-and x y) more)))
37 |
38 | (defn bit-or
39 | "Bitwise or"
40 | ([x y] (.or (long x) (long y)))
41 | ([x y & more] (reduce bit-or (bit-or x y) more)))
42 |
43 | (defn bit-xor
44 | "Bitwise exclusive or"
45 | ([x y] (.xor (long x) (long y)))
46 | ([x y & more] (reduce bit-xor (bit-xor x y) more)))
47 |
48 | (defn bit-not
49 | "Bitwise complement"
50 | ([x y] (.not (long x) (long y)))
51 | ([x y & more] (reduce bit-not (bit-not x y) more)))
52 |
53 | (defn bit-shift-left
54 | "Bitwise shift left"
55 | ([x n] (.shiftLeft (long x) n)))
56 |
57 | (defn bit-shift-right
58 | "Bitwise shift right"
59 | [x n] (.shiftRight (long x) n))
60 |
61 | (defn bit-test
62 | "Test bit at index n"
63 | [x n]
64 | (let [bitv (bit-shift-left 1 n)]
65 | (= bitv (bit-and x bitv))))
66 |
67 | (defn bit-set
68 | "Set bit at index n"
69 | [x n]
70 | (bit-or x (bit-shift-left 1 n)))
71 |
72 | (defn bit-flip
73 | "Flips bit at index n"
74 | [x n]
75 | (bit-xor x (bit-shift-left 1 n)))
76 |
77 | (defn str->long
78 | "Convert string to long with radix support"
79 | ([s] (str->long s 10))
80 | ([s radix]
81 | (.fromString Long s radix)))
82 |
83 | (defn try-str->long
84 | "Try parsing a string and return 0 if not parsable"
85 | [s]
86 | (try
87 | (if (= "0x" (subs s 0 2))
88 | (str->long (subs s 2) 16)
89 | (str->long s))
90 | (catch :default e
91 | (long 0))))
92 |
93 | (defn bits->long
94 | "Convert low and high bits to long"
95 | [low high] (.fromBits Long low high))
96 |
97 | (comment
98 | (= (long 12) 10)
99 | (bit-test 8 3)
100 | (bit-and 12 12)
101 | (bit-and )
102 | (bit-or 10 (.getMaxValue Long))
103 | (str->long "14")
104 | (->hex) (.shiftLeft (try-str->long "0x4A7FFFF7") 32)
105 | (.toString (long (.fromBits Long 0 0x4A7FFFF7)) 16)
106 | (->hex (bit-shift-left (try-str->long "0x1") 32)))
--------------------------------------------------------------------------------
/js/src/airplay/plist.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.plist
2 | (:require [clojure.string :as s]
3 | [clojure.data.xml :as xml]
4 | [airplay.math :as am]
5 | [goog.crypt.base64 :as base64]
6 | [clojure.data.xml.node :as node]
7 | [clojure.data.xml.name :as name]))
8 |
9 | (defn decode-base64 [s]
10 | (base64/decodeStringToByteArray s))
11 | (defn encode-base64 [array]
12 | (base64/encodeByteArray array))
13 |
14 | (defrecord Data [data]
15 | Object
16 | (toString [_] (encode-base64 data)))
17 |
18 | (defn ->data [data]
19 | (Data. (decode-base64 data)))
20 |
21 | (declare dict->map)
22 |
23 | (defn elem->clj [e]
24 | (let [c (:content e)]
25 | (case (:tag e)
26 | :string (s/join "" c)
27 | :integer (am/str->long (s/join "" c))
28 | :real (am/parse-float (s/join "" c))
29 | :true true
30 | :false false
31 | :data (->data (s/join "" c))
32 | :array (map elem->clj c)
33 | :dict (dict->map c)
34 | (throw "Invalid element"))))
35 |
36 | (defn dict->map [e]
37 | (->> e
38 | :content
39 | (filter xml/element?)
40 | (partition 2)
41 | (reduce (fn [val [key value]]
42 | (assoc val (s/join "" (:content key)) (elem->clj value)))
43 | {})))
44 |
45 | (defn parse [s]
46 | (->> s
47 | xml/parse-str
48 | :content
49 | (filter xml/element?)
50 | first
51 | dict->map))
52 |
53 | (declare xml-el)
54 |
55 | (defn- dict-xml
56 | ([el {:keys [indent indent-level] :as opts}]
57 | (let [full (apply str (repeat indent-level indent))]
58 | (mapcat (fn [[key value]]
59 | [full
60 | (node/element :key {} (if (keyword? key) (name key) (str key)))
61 | full
62 | (xml-el value opts)]) el))))
63 |
64 | (defn- pretty-data [data indent]
65 | (if indent
66 | (->> (str data)
67 | (partition 68 68 [])
68 | (map #(str indent (apply str %) "\n"))
69 | (apply str))
70 | (str data)))
71 |
72 | (comment
73 | (pretty-data "test" "\t"))
74 |
75 | (defn- xml-el
76 | ([el] (xml-el el {}))
77 | ([el {:keys [indent indent-level] :as opts}]
78 | (let [full (if indent (apply str (repeat indent-level indent)))
79 | next (assoc opts :indent-level (inc indent-level))
80 | next-full (if indent (apply str (repeat (inc indent-level) indent)))]
81 | (cond
82 | (instance? Data el) (node/element :data {} (pretty-data el full))
83 | (string? el) (node/element :string {} el)
84 | (int? el) (node/element :integer {} (str el))
85 | (double? el) (node/element :real {} (str el))
86 | (= true el) (node/element :true)
87 | (= false el) (node/element :false)
88 | (vector? el) (apply node/element :array {} (mapcat #(concat next-full [(xml-el % next)]) el))
89 | (satisfies? ILookup el) (apply node/element :dict {} (dict-xml el next))
90 | (satisfies? ISequential el) (apply node/element :array {} (mapcat #(concat next-full [(xml-el % next)]) el))
91 | :else (throw (ex-info "Cannot coerce" {:form el}))))))
92 |
93 | (defn xml
94 | ([el] (xml el {}))
95 | ([el opts]
96 | (let [next (assoc opts :indent-level 0)]
97 | (node/element :plist {:version "1.0"} (xml-el el next)))))
98 |
99 | (defn emit-str [el]
100 | (str
101 | ""
102 | ""
103 | (xml/emit-str (xml el))))
104 |
105 | (declare emit-pretty-xml-str)
106 | (defn- element-str [el {:keys [indent indent-level] :as opts}]
107 | (let [qname (name (:tag el))
108 | content (seq (:content el))
109 | full (apply str (repeat indent-level indent))
110 | next (assoc opts :indent-level (inc indent-level))
111 | content-map (if content (map #(emit-pretty-xml-str % next) content))]
112 | (apply str (concat ["<" qname]
113 | (mapcat (fn [[n a]]
114 | [" " (name n) "=" (pr-str a)])
115 | (:attrs el))
116 | (cond
117 | (nil? content) ["/>\n"]
118 | (some node/element? content) (concat [">\n"] content-map [full "" qname ">\n"])
119 | :else (if (re-find #"\n" (apply str content-map))
120 | (concat [">\n"] content-map [full "" qname ">\n"])
121 | (concat [">"] content-map ["" qname ">\n"])))))))
122 | (defn- emit-pretty-xml-str [el opts]
123 | (cond
124 | (string? el) el
125 | (instance? node/Element el) (element-str el opts)))
126 |
127 | (defn emit-pretty-str [el]
128 | (str
129 | "\n"
130 | "\n"
131 | (emit-pretty-xml-str (xml el {:indent "\t"}) {:indent "\t" :indent-level -1})))
132 |
133 | (comment
134 | (name/as-qname (:tag (xml [12 13])))
135 | (emit-pretty-str (am/long 12))
136 | (xml [12 13] {:indent "\t"})
137 | (emit-pretty-str [12 13 {:sdk "test"}])
138 | (emit-pretty-str {:sourceVersion "360.0"})
139 | (emit-pretty-str {:test (->data (str "\tBWFjbD0wGmRldmljZWlkPTAwOjA1OkNEOkQ0OjQyOjk2G2ZlYXR1cmVzPTB4NDQ1RjhB\n"
140 | "\tMDAsMHgxQzM0MAdyc2Y9MHgwEGZ2PXAyMC4xLjUwNS4xMzAJZmxhZ3M9MHg0EG1vZGVs\n"
141 | "\tPUFWUi1YMzUwMEgZbWFudWZhY3R1cmVyPVNvdW5kIFVuaXRlZBtzZXJpYWxOdW1iZXI9\n"
142 | "\tQkJXMzYxODEyMTIzNjQNcHJvdG92ZXJzPTEuMQ1zcmN2ZXJzPTM2Ni4wJ3BpPTY1MGMy\n"
143 | "\tMGVlLTg0MmMtNGMwMS04MGVkLTIyYjdmYjIzOTg4MShnaWQ9NjUwYzIwZWUtODQyYy00\n"
144 | "\tYzAxLTgwZWQtMjJiN2ZiMjM5ODgxBmdjZ2w9MENwaz0wYWNkN2Q2MWIyODRjMGFmNzFj\n"
145 | "\tN2VmNGY3ZWE2NDRkZGRlYzIzOGVmMjdjM2MwYWQzZDVkM2JiOWM4YjMxZThm"))})
146 | (re-find #"\n" "test")
147 | (re-find #"\n"
148 | (str "\tBWFjbD0wGmRldmljZWlkPTAwOjA1OkNEOkQ0OjQyOjk2G2ZlYXR1cmVzPTB4NDQ1RjhB\n"
149 | "\tMDAsMHgxQzM0MAdyc2Y9MHgwEGZ2PXAyMC4xLjUwNS4xMzAJZmxhZ3M9MHg0EG1vZGVs\n"
150 | "\tPUFWUi1YMzUwMEgZbWFudWZhY3R1cmVyPVNvdW5kIFVuaXRlZBtzZXJpYWxOdW1iZXI9\n"
151 | "\tQkJXMzYxODEyMTIzNjQNcHJvdG92ZXJzPTEuMQ1zcmN2ZXJzPTM2Ni4wJ3BpPTY1MGMy\n"
152 | "\tMGVlLTg0MmMtNGMwMS04MGVkLTIyYjdmYjIzOTg4MShnaWQ9NjUwYzIwZWUtODQyYy00\n"
153 | "\tYzAxLTgwZWQtMjJiN2ZiMjM5ODgxBmdjZ2w9MENwaz0wYWNkN2Q2MWIyODRjMGFmNzFj\n"
154 | "\tN2VmNGY3ZWE2NDRkZGRlYzIzOGVmMjdjM2MwYWQzZDVkM2JiOWM4YjMxZThm"))
155 | )
--------------------------------------------------------------------------------
/js/src/airplay/state.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.state
2 | (:require [react :as react]
3 | [reagent.core :as rc]))
4 |
5 |
6 | (defonce context (react/createContext "airplay-state"))
7 | (def Provider (.-Provider context))
8 | (def Consumer (.-Consumer context))
9 |
10 | (deftype App [db]
11 | ILookup
12 | (-lookup [o k]
13 | (case k
14 | :db db))
15 | (-lookup [o k not-found]
16 | (case k
17 | :db db
18 | not-found))
19 | IHash
20 | (-hash [this] (goog/getUid this)))
21 | (defn create-app [initial-value]
22 | (->App (rc/atom initial-value)))
23 |
24 | (defn render-app [app component container]
25 | (js/console.log "Rendering app" app (:db app))
26 | (rc/render [:> Provider {:value app}
27 | [component]
28 | #_[:> Consumer {}
29 | (fn [v]
30 | (rc/as-element [:div "Context: " (get-in v [:db :current :features]) [component]]))]]
31 | container))
32 |
33 |
34 | (defonce subscriptions (atom {}))
35 |
36 | (defn reg-sub [id query-fn]
37 | (swap! subscriptions assoc id query-fn))
38 |
39 | (defonce events (atom {}))
40 |
41 | (defn reg-event-fx [id effect-fn]
42 | (swap! events assoc id effect-fn))
43 |
44 | (defn subscribe [ctx query]
45 | (let [query-fn (get @subscriptions (first query))
46 | db (:db ctx)]
47 | (js/console.log "subscribe Context" ctx query db)
48 | (assert query-fn
49 | (str "first value of query must be registered subscription "
50 | (pr-str (first query))))
51 | (rc/cursor #(query-fn @db %) query)))
52 |
53 |
54 | (defn dispatch [ctx event]
55 | (let [effect-fn (get @events (first event))
56 | db (:db ctx)]
57 | (js/console.log "dispatch Context" ctx event db)
58 | (assert effect-fn
59 | (str "first value of event must be registered event-fx "
60 | (pr-str (first event))))
61 | (swap! db (fn [old] (:db (effect-fn {:db old} event))))))
62 |
63 | (comment
64 | @subscriptions)
--------------------------------------------------------------------------------
/js/src/airplay/ui/airplay_service.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.ui.airplay-service
2 | (:require [clojure.string :as cs]
3 | [dommy.core :as dc :refer-macros [sel sel1]]
4 | [dommy.utils :as du]
5 | [reagent.core :as rc]
6 | [react :as react]
7 | [airplay.features :as af]
8 | [airplay.math :as am]
9 | [airplay.plist :as plist]
10 | [airplay.bits :as ab]
11 | [re-frame.core :as as]
12 | [clojure.data.xml :as xml]
13 | [clojure.string :as s]))
14 |
15 |
16 | (defonce airplay-items
17 | (if-let [table (ab/first-table (sel1 "#_airplay_tcp"))]
18 | (if (not (dc/closest table :#airplay-list))
19 | (ab/table->map table))))
20 |
21 | (comment
22 | airplay-items)
23 |
24 | (defn airplay-table [{:keys [value on-input-changed on-change-bit items]}]
25 | (let [l-value (or @value {})]
26 | [:table {:className "bits"}
27 | ^{:key "head"}
28 | [:tbody {:className ["header"]}
29 | [:tr
30 | ^{:key "name"} [:th "name"]
31 | ^{:key "value"} [:th "value"]
32 | (for [name (->> items vals first keys)]
33 | ^{:key name} [:th name])]]
34 | ^{:key "body"}
35 | [:tbody {:className "rows"}
36 | (for [[key values] items]
37 | ^{:key (name key)}
38 | [:tr
39 | ^{:key "name"} [:td (name key)]
40 | ^{:key "value"} [:td (get l-value key "")]
41 | (for [[key value] values]
42 | ^{:key (name key)} [:td value])])]]))
43 |
44 | (as/reg-event-fx ::change-value
45 | (fn [{:keys [db]} [_ new-value]]
46 | {:db (assoc-in db [:current :services "_airplay._tcp" :txt] new-value)
47 | :dispatch [:airplay.ui.core/current-changed]}))
48 | (as/reg-event-fx ::flip-bit
49 | (fn [{:keys [db]} [_ bit]]
50 | {:db (update-in db [:current :services "_airplay._tcp" :txt]
51 | #(-> % am/try-str->long (am/bit-flip bit) am/->hex))
52 | :dispatch [:airplay.ui.core/current-changed]}))
53 | (as/reg-sub ::value
54 | (fn [db _]
55 | (get-in db [:current :services "_airplay._tcp" :txt])))
56 |
57 | (defn airplay-state []
58 | (js/console.log "airplay-state")
59 | (let [#_ctx #_(.-context (rc/current-component))
60 | value (as/subscribe #_ctx [::value])]
61 | (js/console.log "Component" (rc/current-component) value)
62 | [airplay-table {:items airplay-items
63 | :value value
64 | :on-input-changed (fn [i]
65 | (let [new-value (-> i .-target .-value)]
66 | (js/console.log "Change value" new-value)
67 | (as/dispatch #_ctx [::change-value new-value])))
68 | :on-change-bit #(as/dispatch #_ctx [::flip-bit %])}]))
69 |
70 | (defn airplay-ratom []
71 | (let [value (rc/atom "1")]
72 | [airplay-table {:status-flags airplay-items
73 | :value value
74 | :on-input-changed (fn [i]
75 | (let [new-value (-> i .-target .-value)]
76 | (js/console.log "Change value" new-value)
77 | (reset! value new-value)))
78 | :on-change-bit (fn [bit] (swap! value #(-> % am/try-str->long (am/bit-flip bit) am/->hex)))}]))
79 |
80 |
81 | (defn init []
82 | (if-let [el (sel1 "#_airplay_tcp")]
83 | (let [main (-> el dc/parent)
84 | container (or (sel1 main "#airplay-list")
85 | (let [elem (dc/create-element "div")]
86 | (dc/set-attr! elem "id" "airplay-list")
87 | (dc/replace! (ab/first-table el) elem)))]
88 | (rc/render #_auc/app airplay-state container))))
89 |
--------------------------------------------------------------------------------
/js/src/airplay/ui/core.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.ui.core
2 | (:require [re-frame.core :as as]
3 | [airplay.examples.data :as aed]
4 | [re-frame.core :as rf]))
5 |
6 | #_(defonce app (as/create-app {:current {:features ""}}))
7 |
8 | (rf/reg-event-db ;; sets up initial application state
9 | :initialize ;; usage: (dispatch [:initialize])
10 | (fn [_ _] ;; the two parameters are not important here, so use _
11 | (js/console.log "initialize")
12 | {:current {:features ""} ;; What it returns becomes the new application state
13 | :examples nil
14 | :time-color "#f88"})) ;; so the application state will initially be a map with two keys
15 |
16 | (rf/reg-event-fx ::current-changed
17 | (fn [{:keys [db]} _]
18 | (let [current (:current db)
19 | example (some (fn [[k v]]
20 | (js/console.log "compare" k (= current v) v current)
21 | (if (= current v) k)
22 | ) aed/all)]
23 | (js/console.log "current changed " (pr example))
24 | {:db (assoc db :example example)})))
25 |
--------------------------------------------------------------------------------
/js/src/airplay/ui/examples.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.ui.examples
2 | (:require [re-frame.core :as as]
3 | [airplay.examples.data :as aed]
4 | [reagent.core :as rc]
5 | [dommy.core :as dc]
6 | [airplay.bits :as ab]
7 | [airplay.ui.core :as auc]))
8 |
9 |
10 |
11 | (defn examples-select [{:keys [value examples on-example-changed]}]
12 | (let [selected @value]
13 | (js/console.log "Render example" selected (if selected (name selected) ""))
14 | [:<> "Examples: "
15 | [:select {:on-change on-example-changed :value (if selected (name selected) "")}
16 | [:option {:key "" :value ""} "Custom"]
17 | (for [[id example] examples]
18 | [:option {:key (name id) :value (name id)} (:name example)])]]))
19 |
20 | (as/reg-event-fx ::change-value
21 | (fn [{:keys [db]} [_ new-value]]
22 | (let [example (if-not (= "" new-value) (keyword new-value))
23 | selected (get aed/all example)]
24 | (js/console.log "Change example" example selected)
25 | {:db (assoc db
26 | :example example
27 | :current selected)})))
28 |
29 | (as/reg-sub ::value
30 | (fn [db _]
31 | (get-in db [:example])))
32 |
33 | (defn examples-select-state []
34 | (rc/with-let [#_ctx #_(.-context (rc/current-component))
35 | value (as/subscribe #_ctx [::value])]
36 | (js/console.log "Component" (rc/current-component) value)
37 | [examples-select {:examples aed/all
38 | :value value
39 | :on-example-changed (fn [i]
40 | (let [new-value (-> i .-target .-value)]
41 | (js/console.log "Example change value" new-value)
42 | (as/dispatch #_ctx [::change-value new-value])))}]))
43 |
44 | (defn init []
45 | (if-let [el (dc/sel1 "#examples-select")]
46 | (rc/render #_auc/app examples-select-state el)))
--------------------------------------------------------------------------------
/js/src/airplay/ui/features.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.ui.features
2 | (:require [clojure.string :as cs]
3 | [dommy.core :as dc :refer-macros [sel sel1]]
4 | [dommy.utils :as du]
5 | [reagent.core :as rc]
6 | [react :as react]
7 | [airplay.features :as af]
8 | [airplay.math :as am]
9 | [airplay.plist :as plist]
10 | [airplay.bits :as ab]
11 | [re-frame.core :as as]
12 | [clojure.data.xml :as xml]
13 | [clojure.string :as s]))
14 |
15 |
16 | (defonce features
17 | (if-let [table (ab/first-table (sel1 "#features"))]
18 | (if (not (dc/closest table :#features-list))
19 | (ab/bits-table->map table))))
20 |
21 |
22 | (defn features-table [{:keys [value on-input-changed on-change-bit features]}]
23 | (let [l-value (af/str->features (or @value ""))]
24 | [:table {:className "bits"}
25 | ^{:key "interactive"}
26 | [:tbody {:className ["header"]}
27 | [:tr
28 | [:th {:colSpan 3}
29 | "Example: "
30 | [:input {:type "text"
31 | :on-change on-input-changed
32 | :on-paste on-input-changed
33 | :value @value}]]]]
34 | ^{:key "output"}
35 | [:tbody
36 | [:tr
37 | [:td {:colSpan 3}
38 | "Decimal: " (am/->str l-value)]]
39 | [:tr
40 | [:td {:colSpan 3}
41 | "Hex: " (am/->hex l-value)]]
42 | [:tr
43 | [:td {:colSpan 3}
44 | "mDNS: " (af/features->mdns l-value)]]]
45 | ^{:key "head"}
46 | [:tbody {:className ["header"]}
47 | [:tr
48 | ^{:key "bit"} [:th "bit"]
49 | (for [name (->> features vals first keys)]
50 | ^{:key name} [:th name])]]
51 | ^{:key "body"}
52 | [:tbody {:className "rows"}
53 | (for [[bit values] features]
54 | ^{:key bit}
55 | [:tr {:class (if (am/bit-test l-value bit) "selected" "notSelected")
56 | :on-click #(on-change-bit bit)}
57 | ^{:key "bit"} [:td bit]
58 | (for [[name value] values]
59 | ^{:key name} [:td value])])]]))
60 |
61 | (as/reg-event-fx ::change-value
62 | (fn [{:keys [db]} [_ new-value]]
63 | {:db (assoc-in db [:current :features] new-value)
64 | :dispatch [:airplay.ui.core/current-changed]}))
65 | (as/reg-event-fx ::flip-bit
66 | (fn [{:keys [db]} [_ bit]]
67 | {:db (update-in db [:current :features] #(-> % af/str->features (am/bit-flip bit) af/features->mdns))
68 | :dispatch [:airplay.ui.core/current-changed]}))
69 | (as/reg-sub ::value
70 | (fn [db _]
71 | (get-in db [:current :features])))
72 |
73 | (defn features-state []
74 | (js/console.log "features-state")
75 | (let [#_ctx #_(.-context (rc/current-component))
76 | value (as/subscribe #_ctx [::value])]
77 | (js/console.log "Component" (rc/current-component) value)
78 | [features-table {:features features
79 | :value value
80 | :on-input-changed (fn [i]
81 | (let [new-value (-> i .-target .-value)]
82 | (js/console.log "Change value" new-value)
83 | (as/dispatch #_ctx [::change-value new-value])))
84 | :on-change-bit #(as/dispatch #_ctx [::flip-bit %])}]))
85 |
86 | (defn features-ratom []
87 | (let [value (rc/atom "1")]
88 | [features-table {:features features
89 | :value value
90 | :on-input-changed (fn [i]
91 | (let [new-value (-> i .-target .-value)]
92 | (js/console.log "Change value" new-value)
93 | (reset! value new-value)))
94 | :on-change-bit (fn [bit] (swap! value #(-> % af/str->features (am/bit-flip bit) af/features->mdns)))}]))
95 |
96 |
97 | (defn init []
98 | (if-let [el (sel1 "#features")]
99 | (let [main (-> el dc/parent)
100 | container (or (sel1 main "#features-list")
101 | (let [elem (dc/create-element "div")]
102 | (dc/set-attr! elem "id" "features-list")
103 | (dc/replace! (ab/first-table el) elem)))]
104 | (rc/render #_auc/app features-state container))))
105 |
106 |
107 | (comment
108 | (let [el (sel1 "#features")]
109 | (let [main (-> el dc/parent)
110 | container (or (sel1 main "#features-list")
111 | (let [elem (dc/create-element "div")]
112 | (dc/set-attr! elem "id" "features-list")
113 | (dc/replace! (ab/first-table el) elem)))]
114 | container))
115 | (main)
116 | (ab/first-table (sel1 "#features"))
117 | (map cs/trim (cs/split "0x4A7FFFF7,0x4155FDE" #"," 2))
118 | (am/bit-test (str->features "0x4A7FFFF0,0x4155FDE") 0)
119 | (am/->hex (am/try-str->long "0x4A7FFFF7"))
120 | (am/->hex (am/bit-shift-left (am/try-str->long "0x4A7FFFF7") 24))
121 | (Long )
122 | (let [[low high] (map cs/trim (cs/split "0x4A7FFFF7,0x4155FDE" #"," 2))]
123 | (if high
124 | (am/bit-or (am/try-str->long low) (am/bit-shift-left (am/try-str->long high) 32))
125 | (am/try-str->long low)))
126 | (am/->hex (str->features "0x4A7FFFF7,0x4155FDE"))
127 | (-> (sel1 "#features")
128 | dc/parent
129 | dc/parent
130 | (sel1 :table)))
131 |
132 | (comment
133 | (-> (sel1 "#info code")
134 | dc/text)
135 | (-> (sel1 "#info code")
136 | dc/text
137 | plist/parse)
138 | (let [txt (-> (sel1 "#info code")
139 | dc/text)]
140 | (= (-> txt
141 | plist/parse
142 | plist/emit-pretty-str)
143 | txt))
144 | (first (filter xml/element? (:content (xml/parse-str (dc/text (sel1 "#info code"))))))
145 | (bit-set)
146 | (dc/closest) (map dc/text (sel [:table "td:first-child"])) :#testing
147 | (dc/selector [:thead :th])
148 | (->> (sel (sel1 :table) [:tbody :tr])
149 | (map #(sel1 %1 :td))
150 | (map dc/text)
151 | )
152 | (map #(sel1 %1 :td) (sel (sel1 :table) [:tbody :tr]))
153 | (map dc/text (sel (sel1 :table) [:thead :th]))
154 | (let [table (sel1 :table)]
155 | (if (not (dc/closest table :#features-list))
156 | (bits-table->map table)))
157 | (bits-table->map (sel1 :table))
158 | features
159 | (or (sel1 :#features-list) (sel1 :table)))
--------------------------------------------------------------------------------
/js/src/airplay/ui/flags.cljs:
--------------------------------------------------------------------------------
1 | (ns airplay.ui.flags
2 | (:require [clojure.string :as cs]
3 | [dommy.core :as dc :refer-macros [sel sel1]]
4 | [dommy.utils :as du]
5 | [reagent.core :as rc]
6 | [react :as react]
7 | [airplay.features :as af]
8 | [airplay.math :as am]
9 | [airplay.plist :as plist]
10 | [airplay.bits :as ab]
11 | [re-frame.core :as as]
12 | [clojure.data.xml :as xml]
13 | [clojure.string :as s]))
14 |
15 |
16 | (defonce status-flags
17 | (if-let [table (ab/first-table (sel1 "#status-flags"))]
18 | (if (not (dc/closest table :#status-flags-list))
19 | (ab/bits-table->map table))))
20 |
21 |
22 | (defn status-flags-table [{:keys [value on-input-changed on-change-bit status-flags]}]
23 | (let [l-value (af/str->features (or @value ""))]
24 | [:table {:className "bits"}
25 | ^{:key "interactive"}
26 | [:tbody {:className ["header"]}
27 | [:tr
28 | [:th {:colSpan 3}
29 | "Example: "
30 | [:input {:type "text"
31 | :on-change on-input-changed
32 | :on-paste on-input-changed
33 | :value @value}]]]]
34 | ^{:key "output"}
35 | [:tbody
36 | [:tr
37 | [:td {:colSpan 3}
38 | "Decimal: " (am/->str l-value)]]
39 | [:tr
40 | [:td {:colSpan 3}
41 | "Hex: " (am/->hex l-value)]]]
42 | ^{:key "head"}
43 | [:tbody {:className ["header"]}
44 | [:tr
45 | ^{:key "bit"} [:th "bit"]
46 | (for [name (->> status-flags vals first keys)]
47 | ^{:key name} [:th name])]]
48 | ^{:key "body"}
49 | [:tbody {:className "rows"}
50 | (for [[bit values] status-flags]
51 | ^{:key bit}
52 | [:tr {:class (if (am/bit-test l-value bit) "selected" "notSelected")
53 | :on-click #(on-change-bit bit)}
54 | ^{:key "bit"} [:td bit]
55 | (for [[name value] values]
56 | ^{:key name} [:td value])])]]))
57 |
58 | (as/reg-event-fx ::change-value
59 | (fn [{:keys [db]} [_ new-value]]
60 | {:db (assoc-in db [:current :flags] new-value)
61 | :dispatch [:airplay.ui.core/current-changed]}))
62 | (as/reg-event-fx ::flip-bit
63 | (fn [{:keys [db]} [_ bit]]
64 | {:db (update-in db [:current :flags] #(-> % am/try-str->long (am/bit-flip bit) am/->hex))
65 | :dispatch [:airplay.ui.core/current-changed]}))
66 | (as/reg-sub ::value
67 | (fn [db _]
68 | (get-in db [:current :flags])))
69 |
70 | (defn status-flags-state []
71 | (js/console.log "status-flags-state")
72 | (let [#_ctx #_(.-context (rc/current-component))
73 | value (as/subscribe #_ctx [::value])]
74 | (js/console.log "Component" (rc/current-component) value)
75 | [status-flags-table {:status-flags status-flags
76 | :value value
77 | :on-input-changed (fn [i]
78 | (let [new-value (-> i .-target .-value)]
79 | (js/console.log "Change value" new-value)
80 | (as/dispatch #_ctx [::change-value new-value])))
81 | :on-change-bit #(as/dispatch #_ctx [::flip-bit %])}]))
82 |
83 | (defn status-flags-ratom []
84 | (let [value (rc/atom "1")]
85 | [status-flags-table {:status-flags status-flags
86 | :value value
87 | :on-input-changed (fn [i]
88 | (let [new-value (-> i .-target .-value)]
89 | (js/console.log "Change value" new-value)
90 | (reset! value new-value)))
91 | :on-change-bit (fn [bit] (swap! value #(-> % am/try-str->long (am/bit-flip bit) am/->hex)))}]))
92 |
93 |
94 | (defn init []
95 | (if-let [el (sel1 "#status-flags")]
96 | (let [main (-> el dc/parent)
97 | container (or (sel1 main "#status-flags-list")
98 | (let [elem (dc/create-element "div")]
99 | (dc/set-attr! elem "id" "status-flags-list")
100 | (dc/replace! (ab/first-table el) elem)))]
101 | (rc/render #_auc/app status-flags-state container))))
102 |
--------------------------------------------------------------------------------
/js/src/cards/ui.cljs:
--------------------------------------------------------------------------------
1 | (ns cards.ui
2 | (:require [devcards.core :as dc :include-macros true]
3 | [sablono.core :as sab])
4 | (:require-macros
5 | [devcards.core :refer [defcard]]))
6 |
7 | (defn start []
8 | (js/console.log "Start")
9 | (dc/start-devcard-ui!))
10 |
11 | (js/console.log "UI")
12 |
13 | (defn reload []
14 | (js/console.log "Reload"))
15 |
16 | (defcard my-first-card
17 | (sab/html [:h1 "Devcards is freaking awesome!"]))
18 |
--------------------------------------------------------------------------------
/nix/sources.json:
--------------------------------------------------------------------------------
1 | {
2 | "niv": {
3 | "branch": "master",
4 | "description": "Easy dependency management for Nix projects",
5 | "homepage": "https://github.com/nmattia/niv",
6 | "owner": "nmattia",
7 | "repo": "niv",
8 | "rev": "f73bf8d584148677b01859677a63191c31911eae",
9 | "sha256": "0jlmrx633jvqrqlyhlzpvdrnim128gc81q5psz2lpp2af8p8q9qs",
10 | "type": "tarball",
11 | "url": "https://github.com/nmattia/niv/archive/f73bf8d584148677b01859677a63191c31911eae.tar.gz",
12 | "url_template": "https://github.com/client → server
31 | 32 | ```http 33 | ANNOUNCE rtsp://10.0.1.101/3172942895 RTSP/1.0 34 | CSeq: 1 35 | Content-Type: application/sdp 36 | Content-Length: 567 37 | User-Agent: iTunes/4.6 (Windows; N) 38 | Client-Instance: 9FF35780A8BC8D2B 39 | Apple-Challenge: 09KF45soMYmvj6dpsUGiIg 40 | 41 | v=0 42 | o=iTunes 3172942895 0 IN IP4 10.0.1.101 43 | s=iTunes 44 | c=IN IP4 10.0.1.103 45 | t=0 0 46 | m=audio 0 RTP/AVP 96 47 | a=rtpmap:96 AppleLossless 48 | a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100 49 | a=rsaaeskey:5QYIqmdZGTONY5SHjEJrqAhaa0W9wzDC5i6q221mdGZJ5ubO6Kg 50 | yhC6U83wpY87TFdPRdfPQl2kVC7+Uefmx1bXdIUo07ZcJsqMbgtje4w2JQw0b 51 | Uw2BlzNPmVGQOxfdpGc3LXZzNE0jI1D4conUEiW6rrzikXBhk7Y/i2naw13ayy 52 | xaSwtkiJ0ltBQGYGErbV2tx43QSNj7O0JIG9GrF2GZZ6/UHo4VH+ZXgQ4NZvP/ 53 | QXPCsLutZsvusFDzIEq7TN1fveINOiwrzlN+bckEixvhXlvoQTWE2tjbmQYhMvO 54 | FIly5gNbZiXi0l5AdolX4jDC2vndFHqWDks/3sPikNg 55 | a=aesiv:zcZmAZtqh7uGcEwPXk0QeA 56 | ``` 57 |server → client
60 | 61 | ```http 62 | RTSP/1.0 200 OK 63 | CSeq: 1 64 | Apple-Response: u+msU8Cc7KBrVPjI/Ir8fOL8+C5D3Jsw1+acaW3MNTndrTQAeb/a 65 | 5m10UVBX6wb/DYQGY+b28ksSwBjN0nFOk4Y2cODEf83FAh7B 66 | mkLpmpkpplp7zVXQ+Z9DcB6gC60ZsS3t98aoR7tSzVLKZNgi2X2sC+vGsz 67 | utQxX03HK008VjcdngHv3g1p2knoETd07T6eVfZCmPqp6Ga7Dj8VIIj/GEP3 68 | AjjDx3lJnQBXUDmxM484YXLXZjWFXCiY8GJt6whjf7/2c3rIoT3Z7PQpEvPmM 69 | 1MXU9cv4NL59Y/q0OAVQ38foOz7eGAhfvjOsCnHU25aik7/7ToIYt1tyVtap/kA 70 | Audio-Jack-Status: connected; type=analog 71 | ``` 72 |client → server
5 | 6 | ```http 7 | GET /info RTSP/1.0 8 | X-Apple-ProtocolVersion: 1 9 | CSeq: 0 10 | DACP-ID: ADA239F4521B1802 11 | Active-Remote: 753030410 12 | User-Agent: AirPlay/380.10.1 13 | ``` 14 |server → client
18 | 19 | ```http 20 | RTSP/1.0 200 OK 21 | Content-Length: 927 22 | Content-Type: application/x-apple-binary-plist 23 | Server: AirTunes/366.0 24 | CSeq: 0 25 | 26 |client → server
34 | 35 | ```http 36 | SET_PARAMETER rtsp://fe80::217:f2ff:fe0f:e0f6/3413821438 RTSP/1.0 37 | CSeq: 8 38 | Session: 1 39 | Content-Type: application/x-dmap-tagged 40 | Content-Length: 3242 41 | RTP-Info: rtptime=1146549156 42 | User-Agent: iTunes/10.6 (Macintosh; Intel Mac OS X 10.7.3) AppleWebKit/535.18.5 43 | Client-Instance: 56B29BB6CB904862 44 | DACP-ID: 56B29BB6CB904862 45 | Active-Remote: 1986535575 46 | 47 |server → client
52 | 53 | ```http 54 | RTSP/1.0 200 OK 55 | Server: AirTunes/130.14 56 | CSeq: 8 57 | ``` 58 |client → server
69 | 70 | ```http 71 | SET_PARAMETER rtsp://fe80::217:f2ff:fe0f:e0f6/3413821438 RTSP/1.0 72 | CSeq: 9 73 | Session: 1 74 | Content-Type: image/jpeg 75 | Content-Length: 34616 76 | RTP-Info: rtptime=1146549156 77 | User-Agent: iTunes/10.6 (Macintosh; Intel Mac OS X 10.7.3) AppleWebKit/535.18.5 78 | Client-Instance: 56B29BB6CB904862 79 | DACP-ID: 56B29BB6CB904862 80 | Active-Remote: 1986535575 81 | 82 |server → client
87 | 88 | ```http 89 | RTSP/1.0 200 OK 90 | Server: AirTunes/130.14 91 | CSeq: 9 92 | ``` 93 |client → server
117 | 118 | ```http 119 | SET_PARAMETER rtsp://fe80::217:f2ff:fe0f:e0f6/3413821438 RTSP/1.0 120 | CSeq: 10 121 | Session: 1 122 | Content-Type: text/parameters 123 | Content-Length: 44 124 | User-Agent: iTunes/10.6 (Macintosh; Intel Mac OS X 10.7.3) 125 | AppleWebKit/535.18.5 126 | Client-Instance: 56B29BB6CB904862 127 | DACP-ID: 56B29BB6CB904862 128 | Active-Remote: 1986535575 129 | 130 | progress: 1146221540/1146549156/1195701740 131 | ``` 132 |server → client
135 | 136 | ```http 137 | RTSP/1.0 200 OK 138 | Server: AirTunes/130.14 139 | CSeq: 10 140 | ``` 141 |DACP service from iTunes
14 | 15 | ``` 16 | name: iTunes_Ctrl_56B29BB6CB904862 17 | type: _dacp._tcp 18 | port: 3689 19 | txt: 20 | txtvers=1 21 | Ver=131075 22 | DbId=63B5E5C0C201542E 23 | OSsi=0x1F5 24 | ``` 25 | 26 | Once the DACP server has been identified, HTTP requests can be sent to the 27 | corresponding service port. The `Active-Remote` header must be included in 28 | these requests, so no additional pairing is required. The location for 29 | remote control commands is `/ctrl-int/1/$CMD`. The following commands are 30 | available: 31 | 32 | | command | description | 33 | |---------------|-----------------------------------| 34 | | beginff | begin fast forward | 35 | | beginrew | begin rewind | 36 | | mutetoggle | toggle mute status | 37 | | nextitem | play next item in playlist | 38 | | previtem | play previous item in playlist | 39 | | pause | pause playback | 40 | | playpause | toggle between play and pause | 41 | | play | start playback | 42 | | stop | stop playback | 43 | | playresume | play after fast forward or rewind | 44 | | shuffle_songs | shuffle playlist | 45 | | volumedown | turn audio volume down | 46 | | volumeup | turn audio volume up | 47 | 48 | Example: send a pause command 49 | 50 |server → client
52 | 53 | ```http 54 | GET /ctrl-int/1/pause HTTP/1.1 55 | Host: starlight.local. 56 | Active-Remote: 1986535575 57 | ``` 58 |client → server
61 | 62 | ```http 63 | HTTP/1.1 204 No Content 64 | Date: Tue, 06 Mar 2012 16:38:51 GMT 65 | DAAP-Server: iTunes/10.6 (Mac OS X) 66 | Content-Type: application/x-dmap-tagged 67 | Content-Length: 0 68 | ``` 69 |client → server
27 | 28 | ```hex 29 | 0000 80 e0 b1 91 f7 79 16 c2 e8 bb 6b 2c bb 5c 8e 51 30 | 0010 aa 7c d2 96 00 c3 fd 60 eb ae 6e 41 31 38 fe ae 31 | .... 32 | 03e0 cb 1c 73 bf e7 05 93 30 fa 85 7f 32 77 8d a8 97 33 | 03f0 a0 c7 c8 78 7b e5 81 a1 4f b4 3e a3 43 db 7c 34 | 35 | Real-Time Transport Protocol 36 | 10.. .... = Version: RFC 1889 Version (2) 37 | ..0. .... = Padding: False 38 | ...0 .... = Extension: False 39 | .... 0000 = Contributing source identifiers count: 0 40 | 1... .... = Marker: True 41 | Payload type: DynamicRTP-Type-96 (96) 42 | Sequence number: 45457 43 | Timestamp: 4151908034 44 | Synchronization Source identifier: 0xe8bb6b2c (3904596780) 45 | Payload: bb5c8e51aa7cd29600c3fd60ebae6e413138feae909b44f1... 46 | ``` 47 |client → server
68 | 69 | ```hex 70 | 0000 80 d4 00 04 c7 cd 11 a8 83 ab 1c 49 2f e4 22 e2 71 | 0010 c7 ce 3f 1f 72 | 73 | Real-Time Transport Protocol 74 | 10.. .... = Version: RFC 1889 Version (2) 75 | ..0. .... = Padding: False 76 | ...0 .... = Extension: False 77 | .... 0000 = Contributing source identifiers count: 0 78 | 1... .... = Marker: True 79 | Payload type: Unassigned (84) 80 | Sequence number: 4 81 | Timestamp: 3352105384 82 | Synchronization Source identifier: 0x83ab1c49 (2209029193) 83 | Payload: 2fe422e2c7ce3f1f 84 | ``` 85 |server → client
124 | 125 | ```hex 126 | 0000 80 d2 00 07 00 00 00 00 00 00 00 00 00 00 00 00 127 | 0010 00 00 00 00 00 00 00 00 83 c1 17 cc af ba 9b 32 128 | 129 | Real-Time Transport Protocol 130 | 10.. .... = Version: RFC 1889 Version (2) 131 | ..0. .... = Padding: False 132 | ...0 .... = Extension: False 133 | .... 0000 = Contributing source identifiers count: 0 134 | 1... .... = Marker: True 135 | Payload type: Unassigned (82) 136 | Sequence number: 7 137 | Timestamp: 0 138 | Synchronization Source identifier: 0x00000000 (0) 139 | Payload: 00000000000000000000000083c117ccafba9b32 140 | ``` 141 |client → server
144 | 145 | ```hex 146 | 0000 80 d3 00 07 00 00 00 00 83 c1 17 cc af ba 9b 32 147 | 0010 83 c1 17 cc b0 12 ce b6 83 c1 17 cc b0 14 10 47 148 | 149 | Real-Time Transport Protocol 150 | 10.. .... = Version: RFC 1889 Version (2) 151 | ..0. .... = Padding: False 152 | ...0 .... = Extension: False 153 | .... 0000 = Contributing source identifiers count: 0 154 | 1... .... = Marker: True 155 | Payload type: Unassigned (83) 156 | Sequence number: 7 157 | Timestamp: 0 158 | Synchronization Source identifier: 0x83c117cc (2210469836) 159 | Payload: afba9b3283c117ccb012ceb683c117ccb0141047 160 | ``` 161 |client → server
10 | 11 | ```http 12 | ANNOUNCE rtsp://fe80::217:f2ff:fe0f:e0f6/3413821438 RTSP/1.0 13 | CSeq: 3 14 | Content-Type: application/sdp 15 | Content-Length: 348 16 | User-Agent: iTunes/10.6 (Macintosh; Intel Mac OS X 10.7.3) AppleWebKit/535.18.5 17 | Client-Instance: 56B29BB6CB904862 18 | DACP-ID: 56B29BB6CB904862 19 | Active-Remote: 1986535575 20 | 21 | v=0 22 | o=iTunes 3413821438 0 IN IP4 fe80::217:f2ff:fe0f:e0f6 23 | s=iTunes 24 | c=IN IP4 fe80::5a55:caff:fe1a:e187 25 | t=0 0 26 | m=audio 0 RTP/AVP 96 27 | a=rtpmap:96 AppleLossless 28 | a=fmtp:96 352 0 16 40 10 14 2 255 0 0 44100 29 | a=fpaeskey:RlBMWQECAQAAAAA8AAAAAPFOnNe+zWb5/n4L5KZkE2AAAAAQlDx69reTdwHF9LaNmhiRURTAbcL4brYAceAkZ49YirXm62N4 30 | a=aesiv:5b+YZi9Ikb845BmNhaVo+Q 31 | ``` 32 |server → client
35 | 36 | ```http 37 | RTSP/1.0 200 OK 38 | Server: AirTunes/130.14 39 | CSeq: 3 40 | ``` 41 |client → server
48 | 49 | ```http 50 | ANNOUNCE rtsp://192.168.1.45/2699324803567405959 RTSP/1.0 51 | X-Apple-Device-ID: 0xa4d1d2800b68 52 | CSeq: 16 53 | DACP-ID: 14413BE4996FEA4D 54 | Active-Remote: 2543110914 55 | Content-Type: application/sdp 56 | Content-Length: 331 57 | 58 | v=0 59 | o=AirTunes 2699324803567405959 0 IN IP4 192.168.1.5 60 | s=AirTunes 61 | c=IN IP4 192.168.1.5 62 | t=0 0 63 | m=audio 0 RTP/AVP 96 64 | a=rtpmap:96 mpeg4-generic/44100/2 65 | a=fmtp:96 66 | a=fpaeskey:RlBMWQECAQAAAAA8AAAAAOG6c4aMdLkXAX+lbjp7EhgAAAAQeX5uqGyYkBmJX+gd5ANEr+amI8urqFmvcNo87pR0BXGJ4eLf 67 | a=aesiv:VZTaHn4wSJ84Jjzlb94m0Q== 68 | a=min-latency:11025 69 | ``` 70 |server → client
73 | 74 | ```http 75 | RTSP/1.0 200 OK 76 | Server: AirTunes/130.14 77 | CSeq: 16 78 | ``` 79 |client → server
86 | 87 | ```http 88 | ANNOUNCE rtsp://192.168.1.45/846700446248110360 RTSP/1.0 89 | X-Apple-Device-ID: 0xa4d1d2800b68 90 | CSeq: 27 91 | DACP-ID: 14413BE4996FEA4D 92 | Active-Remote: 2543110914 93 | Content-Type: application/sdp 94 | Content-Length: 415 95 | 96 | v=0 97 | o=AirTunes 846700446248110360 0 IN IP4 192.168.1.5 98 | s=AirTunes 99 | c=IN IP4 192.168.1.5 100 | t=0 0 101 | m=audio 0 RTP/AVP 96 102 | a=rtpmap:96 mpeg4-generic/44100/2 103 | a=fmtp:96 mode=AAC-eld; constantDuration=480 104 | a=fpaeskey:RlBMWQECAQAAAAA8AAAAAKKp+t27A+686xfviEphhw8AAAAQE/3LSqv9MHgnEKxkbKh1buE9+ylKg0YuqcyAC7fT0EqJNtdq 105 | a=aesiv:i/a3nUKYNDSIPP2fC+UKGQ== 106 | a=min-latency:4410 107 | m=video 0 RTP/AVP 97 108 | a=rtpmap:97 H264 109 | a=fmtp:97 110 | ``` 111 |server → client
114 | 115 | ```http 116 | RTSP/1.0 200 OK 117 | Server: AirTunes/130.14 118 | CSeq: 27 119 | ``` 120 |client → server
9 | 10 | ```http 11 | FLUSH rtsp://fe80::217:f2ff:fe0f:e0f6/3413821438 RTSP/1.0 12 | CSeq: 31 13 | Session: 1 14 | RTP-Info: seq=25009;rtptime=1148010660 15 | User-Agent: iTunes/10.6 (Macintosh; Intel Mac OS X 10.7.3) AppleWebKit/535.18.5 16 | Client-Instance: 56B29BB6CB904862 17 | DACP-ID: 56B29BB6CB904862 18 | Active-Remote: 1986535575 19 | ``` 20 |server → client
23 | 24 | ```http 25 | RTSP/1.0 200 OK 26 | RTP-Info: rtptime=1147914212 27 | Server: AirTunes/130.14 28 | CSeq: 31 29 | ``` 30 |client → server
9 | 10 | ```http 11 | FLUSHBUFFERED rtsp://192.168.128.227/4361780817803627124 RTSP/1.0 12 | Content-Length: 87 13 | Content-Type: application/x-apple-binary-plist 14 | CSeq: 12 15 | DACP-ID: C0EF6588CF6D70AC 16 | Active-Remote: 1641836738 17 | User-Agent: AirPlay/387.2 18 | 19 |server → client
24 | 25 | ```http 26 | RTSP/1.0 200 OK 27 | Server: AirTunes/366.0 28 | CSeq: 12 29 | ``` 30 |client → server
36 | 37 | ```http 38 | GET /info RTSP/1.0 39 | X-Apple-ProtocolVersion: 1 40 | CSeq: 0 41 | DACP-ID: ADA239F4521B1802 42 | Active-Remote: 753030410 43 | User-Agent: AirPlay/380.10.1 44 | ``` 45 |server → client
49 | 50 | ```http 51 | RTSP/1.0 200 OK 52 | Content-Length: 927 53 | Content-Type: application/x-apple-binary-plist 54 | Server: AirTunes/366.0 55 | CSeq: 0 56 | 57 |client → server
10 | 11 | ```http 12 | OPTIONS * RTSP/1.0 13 | CSeq: 3 14 | User-Agent: iTunes/10.6 (Macintosh; Intel Mac OS X 10.7.3) AppleWebKit/535.18.5 15 | Client-Instance: 56B29BB6CB904862 16 | DACP-ID: 56B29BB6CB904862 17 | Active-Remote: 1986535575 18 | ``` 19 |server → client
22 | 23 | ```http 24 | RTSP/1.0 200 OK 25 | Public: ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, 26 | GET_PARAMETER, SET_PARAMETER, POST, GET 27 | Server: AirTunes/130.14 28 | CSeq: 3 29 | ``` 30 |client → server
42 | 43 | ```http 44 | POST /auth-setup RTSP/1.0 45 | Content-Type: application/octet-stream 46 | Content-Length: 33 47 | CSeq: 1 48 | User-Agent: iTunes/7.6.2 (Windows; N;) 49 | Client-Instance: 050748FD6F0853AC 50 | DACP-ID: 124D322761E2EC16 51 | Client-instance-identifier: 5743a6b4-e835-412f-9894-14011e386cf2 52 | 53 |server → client
58 | 59 | ```http 60 | RTSP/1.0 200 OK 61 | Content-Length: 1076 62 | Content-Type: application/octet-stream 63 | Server: AirTunes/366.0 64 | CSeq: 1 65 | 66 |client → server
15 | 16 | ```http 17 | RECORD rtsp://fe80::217:f2ff:fe0f:e0f6/3413821438 RTSP/1.0 18 | CSeq: 5 19 | Session: 1 20 | Range: npt=0- 21 | RTP-Info: seq=20857;rtptime=1146549156 22 | User-Agent: iTunes/10.6 (Macintosh; Intel Mac OS X 10.7.3) AppleWebKit/535.18.5 23 | Client-Instance: 56B29BB6CB904862 24 | DACP-ID: 56B29BB6CB904862 25 | Active-Remote: 1986535575 26 | ``` 27 |server → client
30 | 31 | ```http 32 | RTSP/1.0 200 OK 33 | Audio-Latency: 2205 34 | Server: AirTunes/130.14 35 | CSeq: 5 36 | ``` 37 |client → server
16 | 17 | ```http 18 | SETUP rtsp://fe80::217:f2ff:fe0f:e0f6/3413821438 RTSP/1.0 19 | CSeq: 4 20 | Transport: RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;control_port=6001;timing_port=6002 21 | User-Agent: iTunes/10.6 (Macintosh; Intel Mac OS X 10.7.3) AppleWebKit/535.18.5 22 | Client-Instance: 56B29BB6CB904862 23 | DACP-ID: 56B29BB6CB904862 24 | Active-Remote: 1986535575 25 | ``` 26 |server → client
29 | 30 | ```http 31 | RTSP/1.0 200 OK 32 | Transport: RTP/AVP/UDP;unicast;mode=record;server_port=53561;control_port=63379;timing_port=50607 33 | Session: 1 34 | Audio-Jack-Status: connected 35 | Server: AirTunes/130.14 36 | CSeq: 4 37 | ``` 38 |client → server
9 | 10 | ```http 11 | TEARDOWN rtsp://fe80::217:f2ff:fe0f:e0f6/3413821438 RTSP/1.0 12 | CSeq: 32 13 | Session: 1 14 | User-Agent: iTunes/10.6 (Macintosh; Intel Mac OS X 10.7.3) AppleWebKit/535.18.5 15 | Client-Instance: 56B29BB6CB904862 16 | DACP-ID: 56B29BB6CB904862 17 | Active-Remote: 1986535575 18 | ``` 19 |server → client
22 | 23 | ```http 24 | RTSP/1.0 200 OK 25 | Server: AirTunes/130.14 26 | CSeq: 32 27 | ``` 28 |client → server
11 | 12 | ```http 13 | SET_PARAMETER rtsp://fe80::217:f2ff:fe0f:e0f6/3413821438 RTSP/1.0 14 | CSeq: 6 15 | Session: 1 16 | Content-Type: text/parameters 17 | Content-Length: 20 18 | User-Agent: iTunes/10.6 (Macintosh; Intel Mac OS X 10.7.3) AppleWebKit/535.18.5 19 | Client-Instance: 56B29BB6CB904862 20 | DACP-ID: 56B29BB6CB904862 21 | Active-Remote: 1986535575 22 | 23 | volume: -11.123877 24 | ``` 25 |server → client
28 | 29 | ```http 30 | RTSP/1.0 200 OK 31 | Server: AirTunes/130.14 32 | CSeq: 6 33 | ``` 34 |GET /info/
requests. If you have
8 | a computer other than a windows machine I have written some short guides
9 | below and if that is not enough I can probably help you if you send
10 | an e-mail to brian@maven-group.org.
11 |
12 | We right now have data for a couple of 4K Apple TVs, an AirPort Express,
13 | two stereo paired HomePods, the Libretone LTH200 and a Denon AVR-X3500X
14 | surround receiver so if your hardware is something else I am especially
15 | interested in the data from it.
16 |
17 | Any data that you send will only be viewed by me and I will change stuff
18 | like device names, ip addresses, MAC addresses, serial numbers and UUIDs
19 | before including the data in this spec.
20 |
21 | ### Linux
22 |
23 | If you are on Linux and have avahi
installed you can run the following
24 | shell script to gather all the data.
25 |
26 | ```bash
27 | #!/bin/bash
28 | avahi-browse -prt _airplay._tcp | awk -v "FS=;" '{ print $7 }' | sed '/^\s*$/d' > devices.txt
29 | avahi-browse -aprt | grep -f devices.txt > mDNS.txt
30 | for host in $(cat devices.txt) ; do
31 | PORT="$(avahi-browse -prt _airplay._tcp | grep ";$host;" | awk -v "FS=;" '{print $9}' | sed '/^\s*$/d')"
32 | curl https://openairplay.github.io/airplay-spec/data/RTSP-get-info-req.bin | nc -w 2 "$host" $PORT > "RTSP-get-info-res-$host.bin"
33 | done
34 | tar czvf device-data.tar.gz mDNS.txt RTSP-get-info-res*
35 | ```
36 |
37 | Then you can send the resulting file `device-data.tar.gz`
38 | as an e-mail to [brian@maven-group.org](mailto:brian@maven-group.org)
39 |
40 |
41 | ### macOS
42 |
43 | To find mDNS records on macOS I usually use the free
44 | [Discovery](https://itunes.apple.com/dk/app/discovery-dns-sd-browser/id1381004916?mt=12).
45 | From there you can find all the devices that publish a `_airplay._tcp`
46 | service, select and copy/paste the results into an e-mail. It is also
47 | relevant if you can find what other services those devices publish and
48 | include the service records for those services.
49 |
50 | To get the RTSP GET /info
results you will need the ip address and
51 | port number your devices use for `_airplay._tcp` and in the command below
52 | replace `[ip address]`, `[port]` and `[device name]` with the correct values.
53 |
54 |
55 | ```bash
56 | curl https://openairplay.github.io/airplay-spec/data/RTSP-get-info-req.bin | nc -w 2 [ip address] [port] > RTSP-get-info-res-[device name].bin
57 | ```
58 |
59 |
60 | Then you can send the mDNS records and `RTSP-get-info-res-[device name].bin`
61 | files in an e-mail to [brian@maven-group.org](mailto:brian@maven-group.org)
62 |
--------------------------------------------------------------------------------
/src/data/RTSP-get-info-req.bin:
--------------------------------------------------------------------------------
1 | GET /info?txtAirPlay&txtRAOP RTSP/1.0
2 | X-Apple-ProtocolVersion: 1
3 | CSeq: 0
4 | DACP-ID: ADA239F4521B1802
5 | Active-Remote: 753030410
6 | Connection: close
7 | User-Agent: AirPlay/380.10.1
8 |
9 |
--------------------------------------------------------------------------------
/src/data/examples/AirServer/info.plist:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/openairplay/airplay-spec/00063da0e7f16c20fbfb5d29331334da1a89590a/src/data/examples/AirServer/info.plist
--------------------------------------------------------------------------------
/src/data/examples/AirServer/info.plist.base64:
--------------------------------------------------------------------------------
1 | YnBsaXN0MDDeAQIDBAUGBwgJCgsMDQ4PEBElLzAdMjMdNTw9PFhmZWF0dXJl
2 | c1RuYW1lWGRpc3BsYXlzXGF1ZGlvRm9ybWF0c1J2dltzdGF0dXNGbGFnc18Q
3 | EWtlZXBBbGl2ZUxvd1Bvd2VyXXNvdXJjZVZlcnNpb25ScGtfEBhrZWVwQWxp
4 | dmVTZW5kU3RhdHNBc0JvZHleYXVkaW9MYXRlbmNpZXNYZGV2aWNlSURVbW9k
5 | ZWxabWFjQWRkcmVzcxMAAAAOSn//91VBcmxlbqES2hMUFRYXGBkBGhscHR4f
6 | ICEeIiMkXxAScHJpbWFyeUlucHV0RGV2aWNlWHJvdGF0aW9uXXdpZHRoUGh5
7 | c2ljYWxUZWRpZFt3aWR0aFBpeGVsc1R1dWlkXmhlaWdodFBoeXNpY2FsXGhl
8 | aWdodFBpeGVsc1tvdmVyc2Nhbm5lZBABCRAATxCAAP///////wAGEA+gAAAA
9 | ABAVAQSlIRV4Am+xp1VMniUMUFQAAAABAQEBAQEBAQEBAQEBAQEB74NAoLAI
10 | NHAwIDYAS88QAAAaAAAA/ABDb2xvciBMQ0QKICAgAAAAEAAAAAAAAAAAAAAA
11 | AAAAAAAAEAAAAAAAAAAAAAAAAAAAAOcjQJ4AAAAAAABfECQwNjEwMGZhMC03
12 | YjBmLTQzMDUtOTg0Yi05NzRmNjc3YTE1MGIQHiNAkOAAAAAAAAiiJizTJygp
13 | KisrVHR5cGVfEBFhdWRpb0lucHV0Rm9ybWF0c18QEmF1ZGlvT3V0cHV0Rm9y
14 | bWF0cxBkEgP///zTJygpLSsuEGUjQY///+AAAAAQAhAECVYyMjAuNjhfEEBi
15 | NGJmMWU0N2U2YWFjMGI1YmI3Y2Y5ZTJjZDBmZTU5OTRkNGQyZjUwOGE1YmM2
16 | ZDUxODg1NmM4ODQyZjY5YzBhCaI2O9Q3Jzg5Hio6Hl8QE291dHB1dExhdGVu
17 | Y3lNaWNyb3NZYXVkaW9UeXBlXxASaW5wdXRMYXRlbmN5TWljcm9zV2RlZmF1
18 | bHTUNyc4OR4tOh5fEBEyODpDRjpFOToxOTpENjo4NlpBcHBsZVRWNSwzAAgA
19 | JQAuADMAPABJAEwAWABsAHoAfQCYAKcAsAC2AMEAygDQANIA5wD8AQUBEwEY
20 | ASQBKQE4AUUBUQFTAVQBVgHZAeICCQILAhQCFQIYAh8CJAI4Ak0CTwJUAlsC
21 | XQJmAmgCagJrAnICtQK2ArkCwgLYAuIC9wL/AwgDHAAAAAAAAAIBAAAAAAAA
22 | AD4AAAAAAAAAAAAAAAAAAAMn
23 |
--------------------------------------------------------------------------------
/src/data/examples/AirServer/info.plist.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | server → client
17 | 18 | ```http 19 | POST /event HTTP/1.1 20 | Content-Type: text/x-apple-plist+xml 21 | Content-Length: 277 22 | X-Apple-Session-ID: 1bd6ceeb-fffd-456c-a09c-996053a7a08c 23 | ``` 24 | ```xml 25 | 26 | 27 |client → server
42 | 43 | ```http 44 | HTTP/1.1 200 OK 45 | Content-Length: 0 46 | ``` 47 |server → client
64 | 65 | ```http 66 | POST /event HTTP/1.1 67 | Content-Type: text/x-apple-plist+xml 68 | Content-Length: 371 69 | X-Apple-Session-ID: f1634b51-5cae-4384-ade5-54f4159a15f1 70 | ``` 71 | ```xml 72 | 73 | 74 |client → server
90 | 91 | ```http 92 | HTTP/1.1 200 OK 93 | Content-Length: 0 94 | ``` 95 |client → server
12 | 13 | ```http 14 | GET /slideshow-features HTTP/1.1 15 | Accept-Language: English 16 | Content-Length: 0 17 | User-Agent: MediaControl/1.0 18 | X-Apple-Session-ID: cdda804c-33ae-4a0b-a5f2-f0e532fd5abd 19 | ``` 20 |server → client
24 | 25 | ```http 26 | HTTP/1.1 200 OK 27 | Date: Thu, 23 Feb 2012 17:33:41 GMT 28 | Content-Type: text/x-apple-plist+xml 29 | Content-Length: 6411 30 | ``` 31 | ```xml 32 | 33 | 34 |client → server
65 | 66 | ```http 67 | PUT /photo HTTP/1.1 68 | X-Apple-AssetKey: F92F9B91-954E-4D63-BB9A-EEC771ADE6E8 69 | Content-Length: 462848 70 | User-Agent: MediaControl/1.0 71 | X-Apple-Session-ID: 1bd6ceeb-fffd-456c-a09c-996053a7a08c 72 | 73 |server → client
79 | 80 | ```http 81 | HTTP/1.1 200 OK 82 | Date: Thu, 23 Feb 2012 17:33:42 GMT 83 | Content-Length: 0 84 | ``` 85 |client → server
92 | 93 | ```http 94 | PUT /photo HTTP/1.1 95 | X-Apple-AssetKey: F92F9B91-954E-4D63-BB9A-EEC771ADE6E8 96 | X-Apple-Transition: Dissolve 97 | Content-Length: 462848 98 | User-Agent: MediaControl/1.0 99 | X-Apple-Session-ID: 1bd6ceeb-fffd-456c-a09c-996053a7a08c 100 | 101 |server → client
107 | 108 | ```http 109 | HTTP/1.1 200 OK 110 | Date: Thu, 23 Feb 2012 17:33:42 GMT 111 | Content-Length: 0 112 | ``` 113 |client → server
131 | 132 | ```http 133 | PUT /slideshows/1 HTTP/1.1 134 | Content-Type: text/x-apple-plist+xml 135 | Content-Length: 366 136 | User-Agent: MediaControl/1.0 137 | X-Apple-Session-ID: 98a7b246-8e00-49a6-8765-db57165f5b67 138 | ``` 139 | ```xml 140 | 141 | 142 |server → client
160 | 161 | ```http 162 | HTTP/1.1 200 OK 163 | Date: Thu, 08 Mar 2012 16:30:01 GMT 164 | Content-Type: text/x-apple-plist+xml 165 | Content-Length: 181 166 | ``` 167 | ```xml 168 | 169 | 170 |client → server
182 | 183 | ```http 184 | POST /stop HTTP/1.1 185 | Content-Length: 0 186 | User-Agent: MediaControl/1.0 187 | X-Apple-Session-ID: 1bd6ceeb-fffd-456c-a09c-996053a7a08c 188 | ``` 189 |server → client
192 | 193 | ```http 194 | HTTP/1.1 200 OK 195 | Date: Thu, 23 Feb 2012 17:33:55 GMT 196 | Content-Length: 0 197 | ``` 198 |client → server
20 | 21 | ```http 22 | PUT /photo HTTP/1.1 23 | X-Apple-AssetAction: cacheOnly 24 | X-Apple-AssetKey: B0DDE2C0-6FDD-48F8-9E5B-29CE0618DF5B 25 | Content-Length: 462848 26 | User-Agent: MediaControl/1.0 27 | X-Apple-Session-ID: 1bd6ceeb-fffd-456c-a09c-996053a7a08c 28 | 29 |server → client
35 | 36 | ```http 37 | HTTP/1.1 200 OK 38 | Date: Thu, 23 Feb 2012 17:33:45 GMT 39 | Content-Length: 0 40 | ``` 41 |client → server
47 | 48 | ```http 49 | PUT /photo HTTP/1.1 50 | X-Apple-AssetAction: displayCached 51 | X-Apple-AssetKey: B0DDE2C0-6FDD-48F8-9E5B-29CE0618DF5B 52 | X-Apple-Transition: Dissolve 53 | Content-Length: 0 54 | User-Agent: MediaControl/1.0 55 | X-Apple-Session-ID: 1bd6ceeb-fffd-456c-a09c-996053a7a08c 56 | ``` 57 |server → client
61 | 62 | ```http 63 | HTTP/1.1 200 OK 64 | Date: Thu, 23 Feb 2012 17:33:45 GMT 65 | Content-Length: 0 66 | ``` 67 |server → client
20 | 21 | ```http 22 | GET /slideshows/1/assets/1 HTTP/1.1 23 | Content-Length: 0 24 | Accept: application/x-apple-binary-plist 25 | X-Apple-Session-ID: 98a7b246-8e00-49a6-8765-db57165f5b67 26 | ``` 27 |client → server
31 | 32 | ```http 33 | HTTP/1.1 200 OK 34 | Content-Type: application/x-apple-binary-plist 35 | Content-Length: 58932 36 | 37 |client → server
27 | 28 | ```http 29 | GET /stream.xml HTTP/1.1 30 | Content-Length: 0 31 | ``` 32 |server → client
35 | 36 | ```http 37 | HTTP/1.1 200 OK 38 | Date: Mon, 08 Mar 2012 15:30:27 GMT 39 | Content-Type: text/x-apple-plist+xml 40 | Content-Length: 411 41 | ``` 42 | ```xml 43 | 44 | 46 |client → server
95 | 96 | ```http 97 | POST /stream HTTP/1.1 98 | X-Apple-Device-ID: 0xa4d1d2800b68 99 | Content-Length: 503 100 | 101 |H.264 codec data from iPad
32 | 33 | ```hex 34 | 0000 01 64 c0 28 ff e1 00 10 67 64 c0 28 ac 56 20 0d 35 | 0010 81 4f e5 9b 81 01 01 01 01 00 04 28 ee 3c b0 36 | ``` 37 | 38 | The H.264 codec data is interpreted as follows: 39 | 40 | | size | value | description | 41 | |----------|---------|---------------------------| 42 | | 1 byte | 1 | version | 43 | | 1 byte | 100 | profile (high) | 44 | | 1 byte | 0xc0 | compatibility | 45 | | 1 byte | 40 | level (4.0) | 46 | | 6 bits | 0x3f | reserved | 47 | | 2 bits | 3 | NAL units length size - 1 | 48 | | 3 bits | 0x7 | reserved | 49 | | 5 bits | 1 | number of SPS | 50 | | 2 bytes | 16 | length of SPS | 51 | | 16 bytes | … | Sequence parameter set | 52 | | 1 byte | 1 | number of PPS | 53 | | 2 bytes | 4 | length of PPS | 54 | | 4 bytes | … | Picture parameter set | 55 | 56 |Codec data packet from iPad
57 | 58 | ```hex 59 | 0000 1f 00 00 00 01 00 06 00 1d 9a 9f 59 ef de 00 00 60 | 0010 00 00 58 44 00 00 22 44 00 00 00 00 00 00 00 00 61 | 0020 00 00 00 00 00 00 00 00 00 00 58 44 00 00 22 44 62 | 0030 00 00 50 43 00 00 10 42 00 c0 57 44 00 c0 21 44 63 | 0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 64 | 0050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 65 | 0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 66 | 0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 67 | 0080 01 64 c0 28 ff e1 00 10 67 64 c0 28 ac 56 20 0d 68 | 0090 81 4f e5 9b 81 01 01 01 01 00 04 28 ee 3c b0 69 | ``` 70 | 71 | 72 | ## Video Bitstream 73 | 74 | This packet contains the video bitstream to be decoded. The payload can 75 | be optionally AES encrypted. The NTP timestamp found in the header 76 | serves as presentation timestamp. 77 | 78 |Video bitstream packet from iPad
79 | 80 | ```hex 81 | 0000 c8 08 00 00 00 00 06 00 e9 e6 f5 ac 60 e0 00 00 82 | 0010 58 37 6e f9 40 01 00 00 00 00 00 00 00 00 00 00 83 | 0020 00 00 00 00 00 00 00 00 00 00 58 44 00 00 22 44 84 | 0030 00 00 50 43 00 00 10 42 00 c0 57 44 00 c0 21 44 85 | 0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 86 | 0050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 87 | 0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 88 | 0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 89 | 0080 ... 90 | ``` 91 | 92 | 93 | ## Heartbeat 94 | 95 | Sent every second, this packet does not contain any payload. 96 | 97 |Heartbeat packet from iPad
98 | 99 | ```hex 100 | 0000 00 00 00 00 02 00 1e 00 00 00 00 00 00 00 00 00 101 | 0010 4d d8 1a 41 00 00 00 00 00 00 20 41 86 c9 e2 36 102 | 0020 00 00 00 00 80 88 44 4b 00 00 00 00 00 00 00 00 103 | 0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 104 | 0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 105 | 0050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 106 | 0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 107 | 0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 108 | ``` 109 | -------------------------------------------------------------------------------- /src/screen_mirroring/time_synchronization.md: -------------------------------------------------------------------------------- 1 | # Time Synchronization 2 | 3 | Time synchronization takes place on UDP ports 7010 (client) and 7011 4 | (server), using the [NTP] protocol. The AirPlay server runs 5 | an NTP client. Requests are sent to the AirPlay client at 3 second 6 | intervals. The reference date for the timestamps is the beginning of the 7 | mirroring session. 8 | 9 |server → client
11 | 12 | ```http 13 | 0000 23 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 | 0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 15 | 0020 00 00 00 00 00 00 00 00 00 00 01 c4 c8 ac 5d b5 16 | 17 | Network Time Protocol 18 | Flags: 0x23 19 | 00.. .... = Leap Indicator: no warning (0) 20 | ..10 0... = Version number: NTP Version 4 (4) 21 | .... .011 = Mode: client (3) 22 | Peer Clock Stratum: unspecified or invalid (0) 23 | Peer Polling Interval: invalid (0) 24 | Peer Clock Precision: 1.000000 sec 25 | Root Delay: 0.0000 sec 26 | Root Dispersion: 0.0000 sec 27 | Reference ID: NULL 28 | Reference Timestamp: Jan 1, 1970 00:00:00.000000000 UTC 29 | Origin Timestamp: Jan 1, 1970 00:00:00.000000000 UTC 30 | Receive Timestamp: Jan 1, 1970 00:00:00.000000000 UTC 31 | Transmit Timestamp: Jan 1, 1900 00:07:32.783880000 UTC 32 | ``` 33 |client → server
36 | 37 | ```http 38 | 0000 24 01 02 e8 00 00 00 00 00 00 00 00 41 49 52 50 39 | 0010 00 00 00 00 00 00 00 00 00 00 01 c4 c8 ac 5d b5 40 | 0020 00 00 01 c4 c9 6a 0b a1 00 00 01 c4 c9 78 73 d2 41 | 42 | Network Time Protocol 43 | Flags: 0x24 44 | 00.. .... = Leap Indicator: no warning (0) 45 | ..10 0... = Version number: NTP Version 4 (4) 46 | .... .100 = Mode: server (4) 47 | Peer Clock Stratum: primary reference (1) 48 | Peer Polling Interval: invalid (2) 49 | Peer Clock Precision: 0.000000 sec 50 | Root Delay: 0.0000 sec 51 | Root Dispersion: 0.0000 sec 52 | Reference ID: Unidentified reference source 'AIRP' 53 | Reference Timestamp: Jan 1, 1970 00:00:00.000000000 UTC 54 | Origin Timestamp: Jan 1, 1900 00:07:32.783880000 UTC 55 | Receive Timestamp: Jan 1, 1900 00:07:32.786774000 UTC 56 | Transmit Timestamp: Jan 1, 1900 00:07:32.786994000 UTC 57 | ``` 58 |server → client
15 | 16 | ```http 17 | POST /event HTTP/1.1 18 | Content-Type: application/x-apple-plist 19 | Content-Length: 321 20 | X-Apple-Session-ID: 00000000-0000-0000-0000-000000000000 21 | ``` 22 | ```xml 23 | 24 | 25 |client → server
40 | 41 | ```http 42 | HTTP/1.1 200 OK 43 | Content-Length: 0 44 | Date: Mon, 08 Mar 2012 18:07:43 GMT 45 | ``` 46 |