airplay 0.2.9 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/Gemfile.lock +68 -0
- data/HUGS +15 -0
- data/LICENSE +20 -0
- data/README.md +50 -72
- data/Rakefile +6 -9
- data/SPEC.md +34 -0
- data/airplay.gemspec +28 -16
- data/bin/air +27 -0
- data/examples/demo.rb +35 -0
- data/examples/image.rb +9 -0
- data/examples/video.rb +24 -0
- data/lib/airplay.rb +38 -17
- data/lib/airplay/browser.rb +68 -0
- data/lib/airplay/cli.rb +104 -0
- data/lib/airplay/configuration.rb +29 -0
- data/lib/airplay/connection.rb +114 -0
- data/lib/airplay/connection/authentication.rb +37 -0
- data/lib/airplay/connection/persistent.rb +44 -0
- data/lib/airplay/device.rb +81 -0
- data/lib/airplay/device/features.rb +43 -0
- data/lib/airplay/device/info.rb +22 -0
- data/lib/airplay/devices.rb +46 -0
- data/lib/airplay/logger.rb +20 -0
- data/lib/airplay/playable.rb +24 -0
- data/lib/airplay/protocol.rb +9 -72
- data/lib/airplay/protocol/app.rb +52 -0
- data/lib/airplay/protocol/message.rb +8 -0
- data/lib/airplay/protocol/playback_info.rb +49 -0
- data/lib/airplay/protocol/player.rb +170 -0
- data/lib/airplay/protocol/reverse.rb +69 -0
- data/lib/airplay/protocol/slideshow.rb +66 -0
- data/lib/airplay/protocol/timers.rb +25 -0
- data/lib/airplay/protocol/viewer.rb +56 -0
- data/lib/airplay/structure.rb +7 -0
- data/lib/airplay/viewable.rb +16 -0
- data/test/fixtures/cassettes/airplay/listing_slideshow_features.yml +201 -0
- data/test/fixtures/cassettes/airplay/play_an_entire_video.yml +100 -0
- data/test/fixtures/cassettes/airplay/sending_a_video.yml +71 -0
- data/test/fixtures/cassettes/airplay/sending_an_image.yml +26439 -0
- data/test/fixtures/cassettes/airplay/stop_any_transmission.yml +8851 -0
- data/test/fixtures/files/logo.png +0 -0
- data/test/fixtures/files/transition_0.png +0 -0
- data/test/fixtures/files/transition_1.png +0 -0
- data/test/fixtures/files/transition_2.png +0 -0
- data/test/fixtures/files/transition_3.png +0 -0
- data/test/integration/discovery_test.rb +13 -0
- data/test/integration/features_test.rb +14 -0
- data/test/integration/send_media_test.rb +37 -0
- data/test/integration/slideshow_test.rb +26 -0
- data/test/test_helper.rb +42 -0
- data/test/unit/node_test.rb +17 -0
- data/test/unit/protocol_test.rb +44 -0
- metadata +247 -77
- data/.gitignore +0 -2
- data/.travis.yml +0 -6
- data/examples/jobs.jpg +0 -0
- data/examples/send_image.rb +0 -5
- data/examples/send_video.rb +0 -7
- data/lib/airplay/client.rb +0 -60
- data/lib/airplay/protocol/image.rb +0 -41
- data/lib/airplay/protocol/media.rb +0 -64
- data/lib/airplay/protocol/scrub.rb +0 -37
- data/lib/airplay/server.rb +0 -2
- data/lib/airplay/server/browser.rb +0 -35
- data/lib/airplay/server/node.rb +0 -7
- data/test/authentication.rb +0 -13
- data/test/discovery.rb +0 -27
- data/test/fixtures/cassettes/airplay/authenticate_all_the_things_.yml +0 -139
- data/test/fixtures/cassettes/airplay/control_a_video_being_played_in_apple_tv.yml +0 -242
- data/test/fixtures/cassettes/airplay/get_current_scrub_from_apple_tv.yml +0 -69
- data/test/fixtures/cassettes/airplay/go_to_a_given_position_in_the_video.yml +0 -157
- data/test/fixtures/cassettes/airplay/send_audio_to_apple_tv.yml +0 -33
- data/test/fixtures/cassettes/airplay/send_image_to_apple_tv.yml +0 -661
- data/test/fixtures/cassettes/airplay/send_image_to_apple_tv_with_effects.yml +0 -591
- data/test/fixtures/cassettes/airplay/send_video_to_apple_tv.yml +0 -33
- data/test/fixtures/image.gif +0 -0
- data/test/fixtures/image2.gif +0 -0
- data/test/helper.rb +0 -31
- data/test/images.rb +0 -31
- data/test/media.rb +0 -47
- data/test/scrub.rb +0 -31
@@ -0,0 +1,170 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "forwardable"
|
3
|
+
require "micromachine"
|
4
|
+
require "celluloid"
|
5
|
+
require "cfpropertylist"
|
6
|
+
|
7
|
+
require "airplay/connection"
|
8
|
+
require "airplay/protocol/timers"
|
9
|
+
require "airplay/protocol/playback_info"
|
10
|
+
|
11
|
+
module Airplay::Protocol
|
12
|
+
# Public: The class that handles all the video playback
|
13
|
+
#
|
14
|
+
class Player
|
15
|
+
extend Forwardable
|
16
|
+
include Celluloid
|
17
|
+
|
18
|
+
def_delegators :@machine, :state, :on
|
19
|
+
|
20
|
+
def initialize(device)
|
21
|
+
@device = device
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: Plays a given url or file.
|
25
|
+
# Creates a new persistent connection to ensure that
|
26
|
+
# the socket will be kept alive
|
27
|
+
#
|
28
|
+
# file_or_url - The url or file to be reproduced
|
29
|
+
# options - Optional starting time
|
30
|
+
#
|
31
|
+
def play(file_or_url, options = {})
|
32
|
+
start_the_machine
|
33
|
+
check_for_playback_status
|
34
|
+
|
35
|
+
media_url = case true
|
36
|
+
when File.exists?(file_or_url)
|
37
|
+
when !!(file_or_url =~ URI::regexp)
|
38
|
+
file_or_url
|
39
|
+
else
|
40
|
+
raise Errno::ENOENT, file_or_url
|
41
|
+
end
|
42
|
+
|
43
|
+
content = {
|
44
|
+
"Content-Location" => media_url,
|
45
|
+
"Start-Position" => options.fetch(:time, 0.0)
|
46
|
+
}
|
47
|
+
|
48
|
+
data = content.map { |k, v| "#{k}: #{v}" }.join("\r\n")
|
49
|
+
|
50
|
+
response = persistent.async.post("/play", data + "\r\n", {
|
51
|
+
"Content-Type" => "text/parameters"
|
52
|
+
})
|
53
|
+
|
54
|
+
timers.reset
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Handles the progress of the playback, the given &block get's
|
58
|
+
# executed every second while the video is played.
|
59
|
+
#
|
60
|
+
# &block - Block to be executed in every playable second.
|
61
|
+
#
|
62
|
+
def progress(callback)
|
63
|
+
timers << every(1) do
|
64
|
+
callback.call(info) if playing?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Public: Shows the current playback time if a video is being played.
|
69
|
+
#
|
70
|
+
# Returns a hash with the :duration and current :position
|
71
|
+
#
|
72
|
+
def scrub
|
73
|
+
return unless playing?
|
74
|
+
response = connection.get("/scrub")
|
75
|
+
parts = response.body.split("\n")
|
76
|
+
Hash[parts.collect { |v| v.split(": ") }]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Public: checks current playback information
|
80
|
+
#
|
81
|
+
# Returns a hash with the playback information
|
82
|
+
#
|
83
|
+
def info
|
84
|
+
response = connection.get("/playback-info").response
|
85
|
+
plist = CFPropertyList::List.new(data: response.body)
|
86
|
+
hash = CFPropertyList.native_types(plist.value)
|
87
|
+
PlaybackInfo.new(hash)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Public: Resumes a paused video
|
91
|
+
#
|
92
|
+
def resume
|
93
|
+
connection.async.post("/rate?value=1")
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: Pauses a playing video
|
97
|
+
#
|
98
|
+
def pause
|
99
|
+
connection.async.post("/rate?value=0")
|
100
|
+
end
|
101
|
+
|
102
|
+
# Public: Stops the video
|
103
|
+
#
|
104
|
+
def stop
|
105
|
+
connection.post("/stop")
|
106
|
+
end
|
107
|
+
|
108
|
+
def playing?; state == :playing end
|
109
|
+
def paused?; state == :paused end
|
110
|
+
def played?; state == :played end
|
111
|
+
def stopped?; state == :stopped end
|
112
|
+
|
113
|
+
# Public: Locks the execution until the video gets fully played
|
114
|
+
#
|
115
|
+
def wait
|
116
|
+
sleep 0.1 while !played? || stopped?
|
117
|
+
cleanup
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def timers
|
123
|
+
@_timers ||= Timers.new
|
124
|
+
end
|
125
|
+
|
126
|
+
def connection
|
127
|
+
@_connection ||= Airplay::Connection.new(@device)
|
128
|
+
end
|
129
|
+
|
130
|
+
def persistent
|
131
|
+
@_persistent ||= Airplay::Connection.new(@device, keep_alive: true)
|
132
|
+
end
|
133
|
+
|
134
|
+
def cleanup
|
135
|
+
timers.cancel
|
136
|
+
persistent.close
|
137
|
+
end
|
138
|
+
|
139
|
+
def check_for_playback_status
|
140
|
+
timers << every(1) do
|
141
|
+
case true
|
142
|
+
when info.stopped? then @machine.trigger(:stopped) if playing?
|
143
|
+
when info.played? then @machine.trigger(:played) if playing?
|
144
|
+
when info.playing? then @machine.trigger(:playing) if !playing?
|
145
|
+
when info.paused? then @machine.trigger(:paused) if playing?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Private: Get ready the state machine
|
151
|
+
#
|
152
|
+
def start_the_machine
|
153
|
+
@machine = MicroMachine.new(:stopped)
|
154
|
+
|
155
|
+
@machine.on(:stopped) { cleanup }
|
156
|
+
@machine.on(:played) { cleanup }
|
157
|
+
|
158
|
+
@machine.when(:loading, :stopped => :loading)
|
159
|
+
@machine.when(:playing, {
|
160
|
+
:paused => :playing,
|
161
|
+
:loading => :playing,
|
162
|
+
:stopped => :playing
|
163
|
+
})
|
164
|
+
|
165
|
+
@machine.when(:paused, :loading => :paused, :playing => :paused)
|
166
|
+
@machine.when(:stopped, :playing => :stopped, :paused => :stopped)
|
167
|
+
@machine.when(:played, :playing => :played, :paused => :played)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "celluloid"
|
2
|
+
require "net/ptth"
|
3
|
+
require "net/http"
|
4
|
+
require "airplay/protocol/app"
|
5
|
+
require "airplay/connection/authentication"
|
6
|
+
|
7
|
+
module Airplay::Protocol
|
8
|
+
# Public: Handles the reverse connection
|
9
|
+
#
|
10
|
+
class Reverse
|
11
|
+
include Celluloid
|
12
|
+
|
13
|
+
attr_accessor :callbacks
|
14
|
+
attr_reader :state
|
15
|
+
|
16
|
+
def initialize(device, purpose = "event")
|
17
|
+
@logger = Airplay::Logger.new("airplay::protocol::reverse")
|
18
|
+
|
19
|
+
@ptth = Net::PTTH.new("http://#{device.address}")
|
20
|
+
@ptth.set_debug_output = @logger
|
21
|
+
@state = "disconnected"
|
22
|
+
@purpose = purpose
|
23
|
+
@ptth.app = Airplay.app
|
24
|
+
|
25
|
+
@callbacks = []
|
26
|
+
|
27
|
+
async.pipeline
|
28
|
+
@ptth.app.pipeline = self.async
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: Disconnects the current connection
|
32
|
+
#
|
33
|
+
def disconnect
|
34
|
+
@state = "disconnected"
|
35
|
+
@ptth.close
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Connects to the reverse resource and starts the switching
|
39
|
+
#
|
40
|
+
def connect
|
41
|
+
@logger.info "Connecting with purpose: #{@purpose}"
|
42
|
+
|
43
|
+
request = Net::HTTP::Post.new("/reverse")
|
44
|
+
|
45
|
+
request["X-Apple-Purpose"] = @purpose
|
46
|
+
request["X-Apple-Device-ID"] = "0x581faa7c9754"
|
47
|
+
|
48
|
+
if Airplay.active.password?
|
49
|
+
authentication = Airplay::Connection::Authentication.new(@ptth)
|
50
|
+
request = authentication.sign(request)
|
51
|
+
end
|
52
|
+
|
53
|
+
@ptth.request(request)
|
54
|
+
@state = "connected"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: Pipelines all the incomming messages to the callback ppol
|
58
|
+
#
|
59
|
+
def pipeline
|
60
|
+
loop do
|
61
|
+
message = receive { |msg| msg.is_a? Message }
|
62
|
+
@logger.debug "Incomming message from the event pipeline: #{message}"
|
63
|
+
@callbacks.each do |callback|
|
64
|
+
callback.call(message.content)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Airplay::Protocol
|
2
|
+
# Public: Handles the slideshows
|
3
|
+
#
|
4
|
+
class Slideshow
|
5
|
+
def initialize
|
6
|
+
@images = []
|
7
|
+
end
|
8
|
+
|
9
|
+
# Public: Lists all the slideshow features for the active device
|
10
|
+
#
|
11
|
+
# Returns the available themes hash
|
12
|
+
#
|
13
|
+
def features
|
14
|
+
@_features ||= begin
|
15
|
+
response = Airplay.connection.get("/slideshow-features", {
|
16
|
+
"Accept-Language" => "English"
|
17
|
+
})
|
18
|
+
|
19
|
+
plist = CFPropertyList::List.new(data: response.body)
|
20
|
+
native_plist = CFPropertyList.native_types(plist.value)
|
21
|
+
|
22
|
+
native_plist["themes"]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Adds images to the slideshow pool
|
27
|
+
#
|
28
|
+
# images - An array of images to be added
|
29
|
+
#
|
30
|
+
def <<(*images)
|
31
|
+
images.each { |image| @images << image }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: Gets an images given a position on the list
|
35
|
+
#
|
36
|
+
# position - The position on the list
|
37
|
+
#
|
38
|
+
def [](position)
|
39
|
+
@images[position]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Public: Pays the current slideshow
|
43
|
+
#
|
44
|
+
def play
|
45
|
+
@reverse = Airplay::Protocol::Reverse.new(Airplay.active, "slideshow")
|
46
|
+
@reverse.async.connect
|
47
|
+
|
48
|
+
content = {
|
49
|
+
settings: {
|
50
|
+
slideDuration: 3,
|
51
|
+
theme: "Dissolve".upcase
|
52
|
+
},
|
53
|
+
state: "playing"
|
54
|
+
}
|
55
|
+
|
56
|
+
plist = CFPropertyList::List.new
|
57
|
+
plist.value = CFPropertyList.guess(content)
|
58
|
+
binary = plist.to_str(2)
|
59
|
+
|
60
|
+
response = Airplay.connection.async.put("/slideshows/1", binary, {
|
61
|
+
"Content-Type" => "text/x-apple-plist+xml"
|
62
|
+
})
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Airplay::Protocol
|
2
|
+
class Timers
|
3
|
+
include Enumerable
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators :@timers, :each, :size, :empty?
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@timers = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def <<(timer)
|
13
|
+
@timers << timer
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset
|
17
|
+
@timers.each { |t| t.reset }
|
18
|
+
@timers = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def cancel
|
22
|
+
@timers.each { |t| t.cancel }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "open-uri"
|
2
|
+
|
3
|
+
module Airplay::Protocol
|
4
|
+
# Public: The class to handle image broadcast to a device
|
5
|
+
#
|
6
|
+
class Viewer
|
7
|
+
TRANSITIONS = %w(None Dissolve SlideLeft SlideRight)
|
8
|
+
|
9
|
+
def initialize(device)
|
10
|
+
@device = device
|
11
|
+
@logger = Airplay::Logger.new("airplay::protocol::image")
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: Broadcasts the content to the device
|
15
|
+
#
|
16
|
+
def view(media_or_io, options = {})
|
17
|
+
content = get_content(media_or_io)
|
18
|
+
transition = options.fetch(:transition, "None")
|
19
|
+
|
20
|
+
@logger.info "Fetched content (#{content.bytesize} bytes)"
|
21
|
+
@logger.debug "PUT /photo with transition: #{transition}"
|
22
|
+
|
23
|
+
response = connection.put("/photo", content, {
|
24
|
+
"Content-Length" => content.bytesize.to_s,
|
25
|
+
"X-Apple-Transition" => transition
|
26
|
+
})
|
27
|
+
|
28
|
+
response.response.status == 200
|
29
|
+
end
|
30
|
+
|
31
|
+
def transitions; TRANSITIONS end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def connection
|
36
|
+
@_connection ||= Airplay::Connection.new(@device)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Private: Gets the content of the possible media_or_io
|
40
|
+
#
|
41
|
+
# media_or_io - The url, file, path or read compatible source
|
42
|
+
#
|
43
|
+
def get_content(media_or_io)
|
44
|
+
case true
|
45
|
+
when File.exists?(File.expand_path(media_or_io))
|
46
|
+
File.read(media_or_io)
|
47
|
+
when !!(media_or_io =~ URI::regexp)
|
48
|
+
open(media_or_io).read
|
49
|
+
when media_or_io.is_a?(String)
|
50
|
+
media_or_io
|
51
|
+
when media_or_io.respond_to?(:read)
|
52
|
+
media_or_io.read
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "airplay/protocol/viewer"
|
3
|
+
|
4
|
+
module Airplay
|
5
|
+
module Viewable
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :viewer, :view, :transitions
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def viewer
|
13
|
+
@_viewer ||= Airplay::Protocol::Viewer.new(self)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: http://192.168.100.205:7000/slideshow-features
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
base64_string: ''
|
9
|
+
headers:
|
10
|
+
user-agent:
|
11
|
+
- MediaControl/1.0
|
12
|
+
x-apple-session-id:
|
13
|
+
- bee61160-51f2-0130-f43c-705681a9dd8d
|
14
|
+
accept-language:
|
15
|
+
- English
|
16
|
+
connection:
|
17
|
+
- keep-alive
|
18
|
+
keep-alive:
|
19
|
+
- 30
|
20
|
+
response:
|
21
|
+
status:
|
22
|
+
code: 200
|
23
|
+
message: OK
|
24
|
+
headers:
|
25
|
+
date:
|
26
|
+
- Tue, 05 Feb 2013 16:49:51 GMT
|
27
|
+
content-type:
|
28
|
+
- text/x-apple-plist+xml
|
29
|
+
content-length:
|
30
|
+
- '1479'
|
31
|
+
body:
|
32
|
+
encoding: US-ASCII
|
33
|
+
base64_string: ! 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
|
34
|
+
|
35
|
+
WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
|
36
|
+
|
37
|
+
IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
|
38
|
+
|
39
|
+
LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+dGhl
|
40
|
+
|
41
|
+
bWVzPC9rZXk+Cgk8YXJyYXk+CgkJPGRpY3Q+CgkJCTxrZXk+a2V5PC9rZXk+
|
42
|
+
|
43
|
+
CgkJCTxzdHJpbmc+RGlzc29sdmU8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9r
|
44
|
+
|
45
|
+
ZXk+CgkJCTxzdHJpbmc+RGlzc29sdmU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJ
|
46
|
+
|
47
|
+
PGRpY3Q+CgkJCTxrZXk+a2V5PC9rZXk+CgkJCTxzdHJpbmc+U3dpcGVSaWdo
|
48
|
+
|
49
|
+
dDwvc3RyaW5nPgoJCQk8a2V5Pm5hbWU8L2tleT4KCQkJPHN0cmluZz5Td2lw
|
50
|
+
|
51
|
+
ZSBSaWdodDwvc3RyaW5nPgoJCTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5r
|
52
|
+
|
53
|
+
ZXk8L2tleT4KCQkJPHN0cmluZz5Td2lwZUxlZnQ8L3N0cmluZz4KCQkJPGtl
|
54
|
+
|
55
|
+
eT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+U3dpcGUgTGVmdDwvc3RyaW5nPgoJ
|
56
|
+
|
57
|
+
CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
|
58
|
+
|
59
|
+
Zz5SaXBwbGU8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJp
|
60
|
+
|
61
|
+
bmc+UmlwcGxlPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxkaWN0PgoJCQk8a2V5
|
62
|
+
|
63
|
+
PmtleTwva2V5PgoJCQk8c3RyaW5nPlBhZ2VDdXJsPC9zdHJpbmc+CgkJCTxr
|
64
|
+
|
65
|
+
ZXk+bmFtZTwva2V5PgoJCQk8c3RyaW5nPlBhZ2UgQ3VybDwvc3RyaW5nPgoJ
|
66
|
+
|
67
|
+
CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
|
68
|
+
|
69
|
+
Zz5Db3B5TWFjaGluZTwvc3RyaW5nPgoJCQk8a2V5Pm5hbWU8L2tleT4KCQkJ
|
70
|
+
|
71
|
+
PHN0cmluZz5Db3B5IE1hY2hpbmU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJPGRp
|
72
|
+
|
73
|
+
Y3Q+CgkJCTxrZXk+a2V5PC9rZXk+CgkJCTxzdHJpbmc+QmFyc1N3aXBlPC9z
|
74
|
+
|
75
|
+
dHJpbmc+CgkJCTxrZXk+bmFtZTwva2V5PgoJCQk8c3RyaW5nPkJhcnMgU3dp
|
76
|
+
|
77
|
+
cGU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJPGRpY3Q+CgkJCTxrZXk+a2V5PC9r
|
78
|
+
|
79
|
+
ZXk+CgkJCTxzdHJpbmc+RGlzaW50ZWdyYXRlPC9zdHJpbmc+CgkJCTxrZXk+
|
80
|
+
|
81
|
+
bmFtZTwva2V5PgoJCQk8c3RyaW5nPkRpc2ludGVncmF0ZTwvc3RyaW5nPgoJ
|
82
|
+
|
83
|
+
CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
|
84
|
+
|
85
|
+
Zz5Nb2Q8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+
|
86
|
+
|
87
|
+
TW9kPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxkaWN0PgoJCQk8a2V5PmtleTwv
|
88
|
+
|
89
|
+
a2V5PgoJCQk8c3RyaW5nPkZsYXNoPC9zdHJpbmc+CgkJCTxrZXk+bmFtZTwv
|
90
|
+
|
91
|
+
a2V5PgoJCQk8c3RyaW5nPkZsYXNoPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxk
|
92
|
+
|
93
|
+
aWN0PgoJCQk8a2V5PmtleTwva2V5PgoJCQk8c3RyaW5nPk5vbmU8L3N0cmlu
|
94
|
+
|
95
|
+
Zz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+Tm9uZTwvc3RyaW5n
|
96
|
+
|
97
|
+
PgoJCTwvZGljdD4KCTwvYXJyYXk+CjwvZGljdD4KPC9wbGlzdD4K
|
98
|
+
|
99
|
+
'
|
100
|
+
http_version: '1.1'
|
101
|
+
recorded_at: Tue, 05 Feb 2013 18:49:51 GMT
|
102
|
+
- request:
|
103
|
+
method: get
|
104
|
+
uri: http://192.168.100.179:7000/slideshow-features
|
105
|
+
body:
|
106
|
+
encoding: US-ASCII
|
107
|
+
base64_string: ''
|
108
|
+
headers:
|
109
|
+
user-agent:
|
110
|
+
- MediaControl/1.0
|
111
|
+
x-apple-session-id:
|
112
|
+
- 74bc36c0-5213-0130-f46a-705681a9dd8d
|
113
|
+
accept-language:
|
114
|
+
- English
|
115
|
+
connection:
|
116
|
+
- keep-alive
|
117
|
+
keep-alive:
|
118
|
+
- 30
|
119
|
+
response:
|
120
|
+
status:
|
121
|
+
code: 200
|
122
|
+
message: OK
|
123
|
+
headers:
|
124
|
+
date:
|
125
|
+
- Tue, 05 Feb 2013 20:44:00 GMT
|
126
|
+
content-type:
|
127
|
+
- text/x-apple-plist+xml
|
128
|
+
content-length:
|
129
|
+
- '1479'
|
130
|
+
body:
|
131
|
+
encoding: US-ASCII
|
132
|
+
base64_string: ! 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
|
133
|
+
|
134
|
+
WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
|
135
|
+
|
136
|
+
IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
|
137
|
+
|
138
|
+
LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+dGhl
|
139
|
+
|
140
|
+
bWVzPC9rZXk+Cgk8YXJyYXk+CgkJPGRpY3Q+CgkJCTxrZXk+a2V5PC9rZXk+
|
141
|
+
|
142
|
+
CgkJCTxzdHJpbmc+RGlzc29sdmU8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9r
|
143
|
+
|
144
|
+
ZXk+CgkJCTxzdHJpbmc+RGlzc29sdmU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJ
|
145
|
+
|
146
|
+
PGRpY3Q+CgkJCTxrZXk+a2V5PC9rZXk+CgkJCTxzdHJpbmc+U3dpcGVSaWdo
|
147
|
+
|
148
|
+
dDwvc3RyaW5nPgoJCQk8a2V5Pm5hbWU8L2tleT4KCQkJPHN0cmluZz5Td2lw
|
149
|
+
|
150
|
+
ZSBSaWdodDwvc3RyaW5nPgoJCTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5r
|
151
|
+
|
152
|
+
ZXk8L2tleT4KCQkJPHN0cmluZz5Td2lwZUxlZnQ8L3N0cmluZz4KCQkJPGtl
|
153
|
+
|
154
|
+
eT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+U3dpcGUgTGVmdDwvc3RyaW5nPgoJ
|
155
|
+
|
156
|
+
CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
|
157
|
+
|
158
|
+
Zz5SaXBwbGU8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJp
|
159
|
+
|
160
|
+
bmc+UmlwcGxlPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxkaWN0PgoJCQk8a2V5
|
161
|
+
|
162
|
+
PmtleTwva2V5PgoJCQk8c3RyaW5nPlBhZ2VDdXJsPC9zdHJpbmc+CgkJCTxr
|
163
|
+
|
164
|
+
ZXk+bmFtZTwva2V5PgoJCQk8c3RyaW5nPlBhZ2UgQ3VybDwvc3RyaW5nPgoJ
|
165
|
+
|
166
|
+
CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
|
167
|
+
|
168
|
+
Zz5Db3B5TWFjaGluZTwvc3RyaW5nPgoJCQk8a2V5Pm5hbWU8L2tleT4KCQkJ
|
169
|
+
|
170
|
+
PHN0cmluZz5Db3B5IE1hY2hpbmU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJPGRp
|
171
|
+
|
172
|
+
Y3Q+CgkJCTxrZXk+a2V5PC9rZXk+CgkJCTxzdHJpbmc+QmFyc1N3aXBlPC9z
|
173
|
+
|
174
|
+
dHJpbmc+CgkJCTxrZXk+bmFtZTwva2V5PgoJCQk8c3RyaW5nPkJhcnMgU3dp
|
175
|
+
|
176
|
+
cGU8L3N0cmluZz4KCQk8L2RpY3Q+CgkJPGRpY3Q+CgkJCTxrZXk+a2V5PC9r
|
177
|
+
|
178
|
+
ZXk+CgkJCTxzdHJpbmc+RGlzaW50ZWdyYXRlPC9zdHJpbmc+CgkJCTxrZXk+
|
179
|
+
|
180
|
+
bmFtZTwva2V5PgoJCQk8c3RyaW5nPkRpc2ludGVncmF0ZTwvc3RyaW5nPgoJ
|
181
|
+
|
182
|
+
CTwvZGljdD4KCQk8ZGljdD4KCQkJPGtleT5rZXk8L2tleT4KCQkJPHN0cmlu
|
183
|
+
|
184
|
+
Zz5Nb2Q8L3N0cmluZz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+
|
185
|
+
|
186
|
+
TW9kPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxkaWN0PgoJCQk8a2V5PmtleTwv
|
187
|
+
|
188
|
+
a2V5PgoJCQk8c3RyaW5nPkZsYXNoPC9zdHJpbmc+CgkJCTxrZXk+bmFtZTwv
|
189
|
+
|
190
|
+
a2V5PgoJCQk8c3RyaW5nPkZsYXNoPC9zdHJpbmc+CgkJPC9kaWN0PgoJCTxk
|
191
|
+
|
192
|
+
aWN0PgoJCQk8a2V5PmtleTwva2V5PgoJCQk8c3RyaW5nPk5vbmU8L3N0cmlu
|
193
|
+
|
194
|
+
Zz4KCQkJPGtleT5uYW1lPC9rZXk+CgkJCTxzdHJpbmc+Tm9uZTwvc3RyaW5n
|
195
|
+
|
196
|
+
PgoJCTwvZGljdD4KCTwvYXJyYXk+CjwvZGljdD4KPC9wbGlzdD4K
|
197
|
+
|
198
|
+
'
|
199
|
+
http_version: '1.1'
|
200
|
+
recorded_at: Tue, 05 Feb 2013 22:44:00 GMT
|
201
|
+
recorded_with: VCR 2.4.0
|