Class: AirVideo::Client

Inherits:
Object show all
Defined in:
lib/airvideo.rb

Overview

The AirVideo Client. At this stage it can emulate the iPhone app in all major features.

Minor features, such as requesting a specific streams when using the Live Conversion feature aren’t yet supported, but it’s just a question of figuring out what the server is expecting.

Setting #max_height and #max_width should be working with the Live Conversion, but for some reason it’s not quite happening. Defaults are 640x480

Defined Under Namespace

Classes: FolderObject, VideoObject

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(server, port = 45631, password = nil) ⇒ Client

Specify where your AirVideo Server lives. If your HTTP_PROXY environment variable is set, it will be honoured.

At the moment I’m expecting ENV to have the form ‘sub.domain.com:8080’, I throw an http:// and bung it into URI.parse for convenience.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/airvideo.rb', line 25

def initialize(server,port = 45631,password=nil)
  set_proxy # Set to environment proxy settings, if applicable
  @endpoint = URI.parse "http://#{server}:#{port}/service"
  @passworddigest = Digest::SHA1.hexdigest("S@17" + password + "@1r").upcase if !password.nil?
  
  @req = Net::HTTP::Post.new(@endpoint.path)
  @req['User-Agent'] = 'AirVideo/2.2.4 CFNetwork/459 Darwin/10.0.0d3'
  @req['Accept'] = '*/*'
  @req['Accept-Language'] = 'en-us'
  @req['Accept-Encoding'] = 'gzip, deflate'
    
  @current_dir = "/"
  
  @max_width  = 640
  @max_height = 480
end

Instance Attribute Details

#max_heightObject

Returns the value of attribute max_height.



19
20
21
# File 'lib/airvideo.rb', line 19

def max_height
  @max_height
end

#max_widthObject

Returns the value of attribute max_width.



19
20
21
# File 'lib/airvideo.rb', line 19

def max_width
  @max_width
end

#proxyObject (readonly)

Returns the value of attribute proxy.



20
21
22
# File 'lib/airvideo.rb', line 20

def proxy
  @proxy
end

Instance Method Details

#cd(dir) ⇒ Object

Changes to the given directory. Will accept an AirVideo::FolderObject or a string. Returns the AirVideo::Client instance, so you can string commands:

AirVideo::Client.new('127.0.0.1').ls[0].cd.ls

NB. This will not check to see if the folder actually exists!



85
86
87
88
89
# File 'lib/airvideo.rb', line 85

def cd(dir)
  dir = dir.location if dir.is_a? FolderObject
  @current_dir = File.expand_path(dir,@current_dir)
  self
end

#conversion_settings(videoobj) ⇒ Object

private



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/airvideo.rb', line 138

def conversion_settings(videoobj)
  video = videoobj.video_stream
  scaling = [video['width'] / @max_width, video['height'] / @max_height]
  if scaling.max > 1.0
    video['width'] = (video['width'] / scaling.max).to_i
    video['height'] = (video['height'] / scaling.max).to_i
  end
  
  # TODO: fill these in correctly
  AvMap::Hash.new("air.video.ConversionRequest", {
    "itemId" => videoobj.location[1..-1],
    "audioStream"=>1,#videoobj.audio_stream['index'],
    "allowedBitrates"=> AirVideo::AvMap::BitrateList["512", "768", "1536", "1024", "384", "1280", "256"],
    "audioBoost"=>0.0,
    "cropRight"=>0,
    "cropLeft"=>0,
    "resolutionWidth"=>video['width'],
    "videoStream"=>0,#video['index'],
    "cropBottom"=>0,
    "cropTop"=>0,
    "quality"=>0.699999988079071,
    "subtitleInfo"=>nil,
    "offset"=>0.0,
    "resolutionHeight"=>video['height']
  })
end

#get_details(items) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/airvideo.rb', line 106

def get_details(items)
  items = [items] if !items.is_a? Array
  items.collect! do |item|
    case item
    when VideoObject
      item.location[1..-1]
    when String
      item
    end
  end.compact!
  
  request("browseService","getItemsWithDetail",[items])['result'][0]
end

#get_url(videoobj, liveconvert = false) ⇒ Object

Returns the streaming video URL for the given AirVideo::VideoObject.

Raises:

  • (NoMethodError)


92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/airvideo.rb', line 92

def get_url(videoobj,liveconvert = false)
  raise NoMethodError, "Please pass a VideoObject" if not videoobj.is_a? VideoObject
  
  begin
    if liveconvert
      request("livePlaybackService","initLivePlayback",[conversion_settings(videoobj)])['result']['contentURL']
    else
      request("playbackService","initPlayback",[videoobj.location[1..-1]])['result']['contentURL']
    end
  rescue NoMethodError
    raise RuntimeError, "This video does not exist"
  end
end

#inspectObject



133
134
135
# File 'lib/airvideo.rb', line 133

def inspect
  "<AirVideo Connection: #{@endpoint.host}:#{@endpoint.port}>"
end

#ls(dir = ".") ⇒ Object

Lists the folders and videos in the current directory as an Array of AirVideo::VideoObject and AirVideo::FolderObject objects.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/airvideo.rb', line 59

def ls(dir = ".")
  dir = dir.location if dir.is_a? FolderObject
  dir = File.expand_path(dir,@current_dir)[1..-1]
  dir = nil if dir == ""
  begin
    request("browseService","getItems",[dir])['result']['items'].collect do |hash|
      case hash.name
      when "air.video.DiskRootFolder", "air.video.ITunesRootFolder","air.video.Folder"
        FolderObject.new(self,hash['name'],hash['itemId'])
      when "air.video.VideoItem","air.video.ITunesVideoItem"
        VideoObject.new(self,hash['name'],hash['itemId'],hash['detail'] || nil)
      else
        raise NotImplementedError, "Unknown: #{hash.name}"
      end
    end
  rescue NoMethodError
    raise RuntimeError, "This folder does not exist"
  end
end

#pwdObject Also known as: getcwd

Returns the path to the current directory



128
129
130
# File 'lib/airvideo.rb', line 128

def pwd
  @current_dir
end

#request(service, method, params) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/airvideo.rb', line 165

def request(service,method,params)
  avrequest = {
    "requestURL" => @endpoint.to_s,
    "clientVersion" =>221,
    "serviceName" => service,
    "methodName" => method,
    # TODO: Figure out what this is!
    "clientIdentifier" => "89eae483355719f119d698e8d11e8b356525ecfb",
    "parameters" =>params
  }
  avrequest['passwordDigest'] = @passworddigest if not @passworddigest.nil?
  
  @req.body = AvMap::Hash.new("air.connect.Request", avrequest).to_avmap
  
  @http.start(@endpoint.host,@endpoint.port) do |http|
    res = http.request(@req)
    AvMap.parse StringIO.new(res.body)
  end
end

#search(re_string, dir = ".") ⇒ Object

Searches the current directory for items matching the given regular expression



121
122
123
124
125
# File 'lib/airvideo.rb', line 121

def search(re_string,dir=".")
  # Get the directory we're searching
  dir = File.expand_path((dir.is_a? FolderObject) ? dir.location : dir,@current_dir)
  ls(dir).select {|item| item.name =~ %r{#{re_string}}}
end

#set_proxy(proxy_server_and_port = "") ⇒ Object

Potentially confusing:

  • Sending ‘server:port’ will use that address as an HTTP proxy

  • An empty string (or something not recognisable as a URL with http:// put infront of it) will try to use the ENV variable

  • Sending nil or any object that can’t be parsed to a string will remove the proxy

NB. You can access the @proxy URI object externally, but changing it will not automatically call set_proxy



48
49
50
51
52
53
54
55
56
# File 'lib/airvideo.rb', line 48

def set_proxy(proxy_server_and_port = "")
  begin
    @proxy = URI.parse("http://"+((proxy_server_and_port.empty?) ? ENV['HTTP_PROXY'] : string_proxy))
    @http = Net::HTTP::Proxy(@proxy.host, @proxy.port)
  rescue
    @proxy = nil
    @http = Net::HTTP
  end
end