├── Examples
├── .gitignore
├── ControlPointDemo_ObjC
│ ├── ControlPointDemo
│ │ ├── ControlPointDemo-Bridging-Header.h
│ │ ├── Images.xcassets
│ │ │ ├── LaunchImage.launchimage
│ │ │ │ └── Contents.json
│ │ │ ├── play_button.imageset
│ │ │ │ ├── play_button.png
│ │ │ │ ├── play_button@2x.png
│ │ │ │ ├── play_button@3x.png
│ │ │ │ └── Contents.json
│ │ │ ├── stop_button.imageset
│ │ │ │ ├── stop_button.png
│ │ │ │ ├── stop_button@2x.png
│ │ │ │ ├── stop_button@3x.png
│ │ │ │ └── Contents.json
│ │ │ ├── pause_button.imageset
│ │ │ │ ├── pause_button.png
│ │ │ │ ├── pause_button@2x.png
│ │ │ │ ├── pause_button@3x.png
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── RootFolderViewController.h
│ │ ├── AppDelegate.h
│ │ ├── main.m
│ │ ├── Info.plist
│ │ ├── FolderViewController.h
│ │ ├── Player.h
│ │ ├── Base.lproj
│ │ │ └── LaunchScreen.xib
│ │ ├── AppDelegate.m
│ │ ├── FolderViewController.m
│ │ └── Player.m
│ ├── Podfile
│ ├── ControlPointDemo.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── ControlPointDemo.xcscheme
│ ├── ControlPointDemo.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── Podfile.lock
├── ControlPointDemo_Swift
│ ├── ControlPointDemo
│ │ ├── Images.xcassets
│ │ │ ├── play_button.imageset
│ │ │ │ ├── play_button.png
│ │ │ │ ├── play_button@2x.png
│ │ │ │ ├── play_button@3x.png
│ │ │ │ └── Contents.json
│ │ │ ├── stop_button.imageset
│ │ │ │ ├── stop_button.png
│ │ │ │ ├── stop_button@2x.png
│ │ │ │ ├── stop_button@3x.png
│ │ │ │ └── Contents.json
│ │ │ ├── pause_button.imageset
│ │ │ │ ├── pause_button.png
│ │ │ │ ├── pause_button@2x.png
│ │ │ │ ├── pause_button@3x.png
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ ├── Base.lproj
│ │ │ └── LaunchScreen.xib
│ │ ├── AppDelegate.swift
│ │ ├── FolderViewController.swift
│ │ └── Player.swift
│ ├── Podfile
│ ├── ControlPointDemo.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── ControlPointDemo.xcscheme
│ ├── ControlPointDemo.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── Podfile.lock
└── ControlPointDemoMacOS_Swift
│ ├── Podfile
│ ├── ControlPointDemo.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── ControlPointDemo.xcscheme
│ ├── ControlPointDemo.xcworkspace
│ └── contents.xcworkspacedata
│ ├── ControlPointDemo
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Info.plist
│ └── ViewController.swift
│ └── Podfile.lock
├── .gitignore
├── LICENSE
├── Source
├── UPnP Foundation
│ ├── SSDPDiscovery.swift
│ ├── SSDPType.swift
│ ├── UniqueServiceName.swift
│ └── GlobalLib.swift
├── UPnP Objects
│ ├── UPnPEvent.swift
│ ├── UPnPArchivable.swift
│ └── AbstractUPnP.swift
├── HTTP Client Session Managers and Serializers
│ ├── SOAPSessionManager.swift
│ ├── SOAPSerialization.swift
│ └── UPnPEventSubscriptionSerialization.swift
├── Parsers
│ ├── AbstractDOMXMLParser.swift
│ ├── SAXXMLParserElementObservation.swift
│ └── AbstractSAXXMLParser.swift
├── AV Profile
│ ├── Devices
│ │ ├── MediaServer1Device.swift
│ │ └── MediaRenderer1Device.swift
│ ├── Events
│ │ └── AVTransport1Event.swift
│ └── Services
│ │ ├── ConnectionManager1Service.swift
│ │ └── ContentDirectory1Objects.swift
├── SSDP
│ ├── Discovery Adapter
│ │ ├── SSDPDiscoveryAdapter.swift
│ │ └── SSDPExplorerDiscoveryAdapter.swift
│ └── Explorer
│ │ └── SSDPExplorer.swift
├── UPnAtom.swift
├── Logging
│ ├── Logger.swift
│ └── PropertyPrinter.swift
└── CocoaSSDP Support
│ └── CocoaSSDPDiscoveryAdapter.swift
├── UPnAtom.podspec
├── .travis.yml
└── README.md
/Examples/.gitignore:
--------------------------------------------------------------------------------
1 | Pods/
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/ControlPointDemo-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 |
4 | ],
5 | "info" : {
6 | "version" : 1,
7 | "author" : "xcode"
8 | }
9 | }
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/play_button.imageset/play_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/play_button.imageset/play_button.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/play_button.imageset/play_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/play_button.imageset/play_button.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/play_button.imageset/play_button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/play_button.imageset/play_button@2x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/play_button.imageset/play_button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/play_button.imageset/play_button@3x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button@2x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button@3x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/play_button.imageset/play_button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/play_button.imageset/play_button@2x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/play_button.imageset/play_button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/play_button.imageset/play_button@3x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button@2x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/stop_button.imageset/stop_button@3x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button@2x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button@3x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/Podfile:
--------------------------------------------------------------------------------
1 | inhibit_all_warnings!
2 | use_frameworks!
3 | source 'https://github.com/CocoaPods/Specs.git'
4 |
5 | project 'ControlPointDemo'
6 |
7 | target 'ControlPointDemo' do
8 | pod 'UPnAtom', :path => '../..'
9 | end
10 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button@2x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/master-nevi/UPnAtom/HEAD/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/pause_button.imageset/pause_button@3x.png
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/Podfile:
--------------------------------------------------------------------------------
1 | inhibit_all_warnings!
2 | use_frameworks!
3 | source 'https://github.com/CocoaPods/Specs.git'
4 |
5 | project 'ControlPointDemo'
6 |
7 | target 'ControlPointDemo' do
8 | pod 'UPnAtom', :path => '../..'
9 | end
10 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemoMacOS_Swift/Podfile:
--------------------------------------------------------------------------------
1 | inhibit_all_warnings!
2 | use_frameworks!
3 | source 'https://github.com/CocoaPods/Specs.git'
4 |
5 | project 'ControlPointDemo'
6 |
7 | target 'ControlPointDemo' do
8 | pod 'UPnAtom', :path => '../..'
9 | end
10 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemoMacOS_Swift/ControlPointDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemoMacOS_Swift/ControlPointDemo.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/pause_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "pause_button.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "pause_button@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "pause_button@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/play_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "play_button.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "play_button@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "play_button@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/stop_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "stop_button.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "stop_button@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "stop_button@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/play_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "play_button.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "play_button@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "play_button@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/stop_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "stop_button.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "stop_button@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "stop_button@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/pause_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "pause_button.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "pause_button@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "pause_button@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 |
20 | # CocoaPods
21 | #
22 | # We recommend against adding the Pods directory to your .gitignore. However
23 | # you should judge for yourself, the pros and cons are mentioned at:
24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
25 | #
26 | # Pods/
27 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemoMacOS_Swift/ControlPointDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ControlPointDemo
4 | //
5 | // Created by David Robles on 8/21/16.
6 | // Copyright © 2016 David Robles. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 |
15 |
16 | func applicationDidFinishLaunching(aNotification: NSNotification) {
17 | // Insert code here to initialize your application
18 | }
19 |
20 | func applicationWillTerminate(aNotification: NSNotification) {
21 | // Insert code here to tear down your application
22 | }
23 |
24 |
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 master-nevi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemoMacOS_Swift/ControlPointDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/Examples/ControlPointDemoMacOS_Swift/ControlPointDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2016 David Robles. All rights reserved.
29 | NSMainStoryboardFile
30 | Main
31 | NSPrincipalClass
32 | NSApplication
33 | NSAppTransportSecurity
34 |
35 | NSAllowsArbitraryLoads
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/RootFolderViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // RootFolderViewController.h
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | @import UIKit;
25 |
26 | @interface RootFolderViewController : UIViewController
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | @import UIKit;
25 |
26 | @interface AppDelegate : UIResponder
27 |
28 | @property (strong, nonatomic) UIWindow *window;
29 |
30 |
31 | @end
32 |
33 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | @import UIKit;
25 | #import "AppDelegate.h"
26 |
27 | int main(int argc, char * argv[]) {
28 | @autoreleasepool {
29 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | NSAppTransportSecurity
40 |
41 | NSAllowsArbitraryLoads
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | NSAppTransportSecurity
40 |
41 | NSAllowsArbitraryLoads
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/FolderViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // FolderViewController.h
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | @import UIKit;
25 |
26 | @class MediaServer1Device;
27 |
28 | @interface FolderViewController : UIViewController
29 |
30 | - (void)configureWithDevice:(MediaServer1Device *)device header:(NSString *)header rootId:(NSString *)rootId;
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/Source/UPnP Foundation/SSDPDiscovery.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SSDPDiscovery.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | struct SSDPDiscovery {
25 | let usn: UniqueServiceName
26 | let descriptionURL: NSURL
27 | let type: SSDPType
28 | }
29 |
30 | extension SSDPDiscovery: Equatable { }
31 |
32 | func ==(lhs: SSDPDiscovery, rhs: SSDPDiscovery) -> Bool {
33 | return lhs.usn == rhs.usn
34 | }
35 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemoMacOS_Swift/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - AFNetworking (3.1.0):
3 | - AFNetworking/NSURLSession (= 3.1.0)
4 | - AFNetworking/Reachability (= 3.1.0)
5 | - AFNetworking/Security (= 3.1.0)
6 | - AFNetworking/Serialization (= 3.1.0)
7 | - AFNetworking/UIKit (= 3.1.0)
8 | - AFNetworking/NSURLSession (3.1.0):
9 | - AFNetworking/Reachability
10 | - AFNetworking/Security
11 | - AFNetworking/Serialization
12 | - AFNetworking/Reachability (3.1.0)
13 | - AFNetworking/Security (3.1.0)
14 | - AFNetworking/Serialization (3.1.0)
15 | - CocoaAsyncSocket (7.4.3):
16 | - CocoaAsyncSocket/All (= 7.4.3)
17 | - CocoaAsyncSocket/All (7.4.3):
18 | - CocoaAsyncSocket/GCD
19 | - CocoaAsyncSocket/RunLoop
20 | - CocoaAsyncSocket/GCD (7.4.3)
21 | - CocoaAsyncSocket/RunLoop (7.4.3)
22 | - GCDWebServer (3.3.3):
23 | - GCDWebServer/Core (= 3.3.3)
24 | - GCDWebServer/Core (3.3.3)
25 | - Ono (1.2.2)
26 | - UPnAtom (0.7.0):
27 | - AFNetworking (~> 3.1)
28 | - CocoaAsyncSocket (~> 7.4.1)
29 | - GCDWebServer (~> 3.3)
30 | - Ono (~> 1.2.0)
31 |
32 | DEPENDENCIES:
33 | - UPnAtom (from `../..`)
34 |
35 | EXTERNAL SOURCES:
36 | UPnAtom:
37 | :path: "../.."
38 |
39 | SPEC CHECKSUMS:
40 | AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67
41 | CocoaAsyncSocket: a18c75dca4b08723628a0bacca6e94803d90be91
42 | GCDWebServer: 1c39a1f0763e4eb492bee021e4270fce097d3555
43 | Ono: 80b1c686777d3f6d5d5ce6fcac8a74afef4ca197
44 | UPnAtom: 3c96820e2f784fb45116c9cd0e2feebb4f960c87
45 |
46 | PODFILE CHECKSUM: 0c01f4122f5037d4cf76b9400cce28aeada874ce
47 |
48 | COCOAPODS: 1.0.1
49 |
--------------------------------------------------------------------------------
/Source/UPnP Objects/UPnPEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UPnPEvent.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | /// TODO: For now rooting to NSObject to expose to Objective-C, see Github issue #16
27 | public class UPnPEvent: NSObject {
28 | public let eventXML: NSData
29 | public weak var service: AbstractUPnPService?
30 | init(eventXML: NSData, service: AbstractUPnPService) {
31 | self.eventXML = eventXML
32 | self.service = service
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - AFNetworking (3.1.0):
3 | - AFNetworking/NSURLSession (= 3.1.0)
4 | - AFNetworking/Reachability (= 3.1.0)
5 | - AFNetworking/Security (= 3.1.0)
6 | - AFNetworking/Serialization (= 3.1.0)
7 | - AFNetworking/UIKit (= 3.1.0)
8 | - AFNetworking/NSURLSession (3.1.0):
9 | - AFNetworking/Reachability
10 | - AFNetworking/Security
11 | - AFNetworking/Serialization
12 | - AFNetworking/Reachability (3.1.0)
13 | - AFNetworking/Security (3.1.0)
14 | - AFNetworking/Serialization (3.1.0)
15 | - AFNetworking/UIKit (3.1.0):
16 | - AFNetworking/NSURLSession
17 | - CocoaAsyncSocket (7.4.3):
18 | - CocoaAsyncSocket/All (= 7.4.3)
19 | - CocoaAsyncSocket/All (7.4.3):
20 | - CocoaAsyncSocket/GCD
21 | - CocoaAsyncSocket/RunLoop
22 | - CocoaAsyncSocket/GCD (7.4.3)
23 | - CocoaAsyncSocket/RunLoop (7.4.3)
24 | - GCDWebServer (3.3.3):
25 | - GCDWebServer/Core (= 3.3.3)
26 | - GCDWebServer/Core (3.3.3)
27 | - Ono (1.2.2)
28 | - UPnAtom (0.7.0):
29 | - AFNetworking (~> 3.1)
30 | - CocoaAsyncSocket (~> 7.4.1)
31 | - GCDWebServer (~> 3.3)
32 | - Ono (~> 1.2.0)
33 |
34 | DEPENDENCIES:
35 | - UPnAtom (from `../..`)
36 |
37 | EXTERNAL SOURCES:
38 | UPnAtom:
39 | :path: "../.."
40 |
41 | SPEC CHECKSUMS:
42 | AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67
43 | CocoaAsyncSocket: a18c75dca4b08723628a0bacca6e94803d90be91
44 | GCDWebServer: 1c39a1f0763e4eb492bee021e4270fce097d3555
45 | Ono: 80b1c686777d3f6d5d5ce6fcac8a74afef4ca197
46 | UPnAtom: 3c96820e2f784fb45116c9cd0e2feebb4f960c87
47 |
48 | PODFILE CHECKSUM: 0c01f4122f5037d4cf76b9400cce28aeada874ce
49 |
50 | COCOAPODS: 1.0.1
51 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - AFNetworking (3.1.0):
3 | - AFNetworking/NSURLSession (= 3.1.0)
4 | - AFNetworking/Reachability (= 3.1.0)
5 | - AFNetworking/Security (= 3.1.0)
6 | - AFNetworking/Serialization (= 3.1.0)
7 | - AFNetworking/UIKit (= 3.1.0)
8 | - AFNetworking/NSURLSession (3.1.0):
9 | - AFNetworking/Reachability
10 | - AFNetworking/Security
11 | - AFNetworking/Serialization
12 | - AFNetworking/Reachability (3.1.0)
13 | - AFNetworking/Security (3.1.0)
14 | - AFNetworking/Serialization (3.1.0)
15 | - AFNetworking/UIKit (3.1.0):
16 | - AFNetworking/NSURLSession
17 | - CocoaAsyncSocket (7.4.3):
18 | - CocoaAsyncSocket/All (= 7.4.3)
19 | - CocoaAsyncSocket/All (7.4.3):
20 | - CocoaAsyncSocket/GCD
21 | - CocoaAsyncSocket/RunLoop
22 | - CocoaAsyncSocket/GCD (7.4.3)
23 | - CocoaAsyncSocket/RunLoop (7.4.3)
24 | - GCDWebServer (3.3.3):
25 | - GCDWebServer/Core (= 3.3.3)
26 | - GCDWebServer/Core (3.3.3)
27 | - Ono (1.2.2)
28 | - UPnAtom (0.7.0):
29 | - AFNetworking (~> 3.1)
30 | - CocoaAsyncSocket (~> 7.4.1)
31 | - GCDWebServer (~> 3.3)
32 | - Ono (~> 1.2.0)
33 |
34 | DEPENDENCIES:
35 | - UPnAtom (from `../..`)
36 |
37 | EXTERNAL SOURCES:
38 | UPnAtom:
39 | :path: "../.."
40 |
41 | SPEC CHECKSUMS:
42 | AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67
43 | CocoaAsyncSocket: a18c75dca4b08723628a0bacca6e94803d90be91
44 | GCDWebServer: 1c39a1f0763e4eb492bee021e4270fce097d3555
45 | Ono: 80b1c686777d3f6d5d5ce6fcac8a74afef4ca197
46 | UPnAtom: 3c96820e2f784fb45116c9cd0e2feebb4f960c87
47 |
48 | PODFILE CHECKSUM: 0c01f4122f5037d4cf76b9400cce28aeada874ce
49 |
50 | COCOAPODS: 1.0.1
51 |
--------------------------------------------------------------------------------
/Source/HTTP Client Session Managers and Serializers/SOAPSessionManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SOAPSessionManager.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 | import AFNetworking
26 |
27 | public class SOAPSessionManager: AFHTTPSessionManager {
28 | override init(baseURL url: NSURL?, sessionConfiguration configuration: NSURLSessionConfiguration?) {
29 | super.init(baseURL: url, sessionConfiguration: configuration)
30 |
31 | self.requestSerializer = SOAPRequestSerializer()
32 | self.responseSerializer = SOAPResponseSerializer()
33 | }
34 |
35 | required public init?(coder aDecoder: NSCoder) {
36 | super.init(coder: aDecoder)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Player.h:
--------------------------------------------------------------------------------
1 | //
2 | // Player.h
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | @import Foundation;
25 | @import UIKit;
26 |
27 | @class MediaServer1Device;
28 | @class MediaRenderer1Device;
29 |
30 | @interface Player : NSObject
31 |
32 | + (Player *)sharedInstance;
33 |
34 | - (void)startPlayback:(NSArray *)playList position:(NSInteger)position;
35 | - (void)startPlayback:(NSInteger)position;
36 |
37 | @property (nonatomic, readonly) NSArray *playlist;
38 | @property (nonatomic) MediaServer1Device *mediaServer;
39 | @property (nonatomic) MediaRenderer1Device *mediaRenderer;
40 | @property (nonatomic, readonly) UIBarButtonItem *playPauseButton;
41 | @property (nonatomic, readonly) UIBarButtonItem *stopButton;
42 |
43 | @end
44 |
45 |
--------------------------------------------------------------------------------
/Source/Parsers/AbstractDOMXMLParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AbstractDOMXMLParser.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 | import Ono
26 |
27 | public class AbstractDOMXMLParser {
28 | public func parse(data data: NSData) -> EmptyResult {
29 | LogVerbose("Parsing XML:\nSTART\n\(NSString(data: data, encoding: NSUTF8StringEncoding))\nEND")
30 |
31 | var parserResult: EmptyResult = .Failure(createError("Parser failure"))
32 | autoreleasepool { () -> () in
33 | do {
34 | parserResult = self.parse(document:try ONOXMLDocument(data: data))
35 | } catch let parseError as NSError {
36 | parserResult = EmptyResult.Failure(parseError)
37 | }
38 | }
39 |
40 | return parserResult
41 | }
42 |
43 | public func parse(document document: ONOXMLDocument) -> EmptyResult {
44 | fatalError("Implement in subclass")
45 | }
46 |
47 | // MARK: - Internal lib
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/Source/AV Profile/Devices/MediaServer1Device.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediaServer1Device.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | public class MediaServer1Device: AbstractUPnPDevice {
27 | public var avTransportService: AVTransport1Service? {
28 | return service(forURN: "urn:schemas-upnp-org:service:AVTransport:1") as? AVTransport1Service
29 | }
30 |
31 | public var connectionManagerService: ConnectionManager1Service? {
32 | return service(forURN: "urn:schemas-upnp-org:service:ConnectionManager:1") as? ConnectionManager1Service
33 | }
34 |
35 | public var contentDirectoryService: ContentDirectory1Service? {
36 | return service(forURN: "urn:schemas-upnp-org:service:ContentDirectory:1") as? ContentDirectory1Service
37 | }
38 | }
39 |
40 | /// for objective-c type checking
41 | extension AbstractUPnP {
42 | public func isMediaServer1Device() -> Bool {
43 | return self is MediaServer1Device
44 | }
45 | }
46 |
47 | /// overrides ExtendedPrintable protocol implementation
48 | extension MediaServer1Device {
49 | override public var className: String { return "\(self.dynamicType)" }
50 | override public var description: String {
51 | var properties = PropertyPrinter()
52 | properties.add(super.className, property: super.description)
53 | return properties.description
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Source/AV Profile/Devices/MediaRenderer1Device.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediaRenderer1Device.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | public class MediaRenderer1Device: AbstractUPnPDevice {
27 | public var avTransportService: AVTransport1Service? {
28 | return service(forURN: "urn:schemas-upnp-org:service:AVTransport:1") as? AVTransport1Service
29 | }
30 |
31 | public var connectionManagerService: ConnectionManager1Service? {
32 | return service(forURN: "urn:schemas-upnp-org:service:ConnectionManager:1") as? ConnectionManager1Service
33 | }
34 |
35 | public var renderingControlService: RenderingControl1Service? {
36 | return service(forURN: "urn:schemas-upnp-org:service:RenderingControl:1") as? RenderingControl1Service
37 | }
38 | }
39 |
40 | /// for objective-c type checking
41 | extension AbstractUPnP {
42 | public func isMediaRenderer1Device() -> Bool {
43 | return self is MediaRenderer1Device
44 | }
45 | }
46 |
47 | /// overrides ExtendedPrintable protocol implementation
48 | extension MediaRenderer1Device {
49 | override public var className: String { return "\(self.dynamicType)" }
50 | override public var description: String {
51 | var properties = PropertyPrinter()
52 | properties.add(super.className, property: super.description)
53 | return properties.description
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Source/SSDP/Discovery Adapter/SSDPDiscoveryAdapter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AVTransport1Event.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | protocol SSDPDiscoveryAdapterDelegate: class {
25 | func ssdpDiscoveryAdapter(adapter: SSDPDiscoveryAdapter, didUpdateSSDPDiscoveries ssdpDiscoveries: [SSDPDiscovery])
26 | /// Assume discovery adapter has stopped after a failure.
27 | func ssdpDiscoveryAdapter(adapter: SSDPDiscoveryAdapter, didFailWithError error: NSError)
28 | }
29 |
30 | /// Provides an interface to allow any SSDP library to be used an adapted into UPnAtom for SSDP discovery.
31 | protocol SSDPDiscoveryAdapter: class {
32 | var rawSSDPTypes: Set { get set }
33 | weak var delegate: SSDPDiscoveryAdapterDelegate? { get set }
34 | var running: Bool { get }
35 | func start()
36 | func stop()
37 | func restart()
38 | }
39 |
40 | /// An abstract class to allow any SSDP library to be used an adapted into UPnAtom for SSDP discovery.
41 | class AbstractSSDPDiscoveryAdapter: SSDPDiscoveryAdapter {
42 | var rawSSDPTypes: Set = []
43 | weak var delegate: SSDPDiscoveryAdapterDelegate?
44 | var delegateQueue = dispatch_get_main_queue()
45 | private(set) var running = false
46 |
47 | required init() { }
48 |
49 | func start() {
50 | running = true
51 | }
52 |
53 | func stop() {
54 | running = false
55 | }
56 |
57 | func restart() {
58 | if running {
59 | stop()
60 | }
61 | start()
62 | }
63 |
64 | /// 🔰 = protected
65 | ///
66 | /// Sets running = false.
67 | func failed🔰() {
68 | running = false
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/UPnAtom.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'UPnAtom'
3 | s.version = '0.7.0'
4 | s.license = { :type => 'MIT', :file => 'LICENSE' }
5 | s.summary = 'An open source Universal Plug and Play library with a focus on media streaming coordination using the UPnP A/V profile.'
6 | s.homepage = 'https://github.com/master-nevi/UPnAtom'
7 | s.authors = { 'David Robles' => 'master-nevi@users.noreply.github.com' }
8 | s.source = { :git => 'https://github.com/master-nevi/UPnAtom.git', :tag => s.version.to_s }
9 | s.requires_arc = true
10 | s.ios.deployment_target = '8.0'
11 | s.osx.deployment_target = '10.9'
12 |
13 | s.source_files = 'Source/**/*.swift'
14 | s.exclude_files = 'Source/CocoaSSDP Support/*.swift'
15 | s.dependency 'CocoaAsyncSocket', '~> 7.4.1' # UPnP object discovery using SSDP
16 | s.dependency 'AFNetworking', '~> 3.1' # HTTP Client
17 | s.dependency 'Ono', '~> 1.2.0' # XML parsing
18 | s.dependency 'GCDWebServer', '~> 3.3' # UPnP event notification handling
19 |
20 |
21 | # NOTE: I really did try to be a good pod architect and modularize the library into subspecs, however there are still bugs in the Swift compiler which cause it to crash when building release/archive versions of UPnAtom. Because the problem areas may consistantly or inconsistanly cause the compiler crash, the fewer subspecs or no subspecs the better when it comes to tracking them down (i.e. less hair being pulled out). I look forward to doing it in the future however. Here's a sneak peek:
22 |
23 | # s.default_subspecs = 'Default'
24 |
25 | # s.subspec 'Default' do |ss|
26 | # ss.source_files = 'Source/UPnAtom.swift'
27 | # ss.dependency 'UPnAtom/AV Profile'
28 | # end
29 |
30 | # s.subspec 'UPnP Foundation' do |ss|
31 | # ss.source_files = 'Source/UPnP Foundation/*.swift'
32 | # end
33 |
34 | # s.subspec 'SSDP Explorer' do |ss|
35 | # ss.source_files = 'Source/SSDP/Explorer/*.swift'
36 | # ss.dependency 'UPnAtom/UPnP Foundation'
37 | # ss.dependency 'CocoaAsyncSocket', '~> 7.4.1' # UPnP object discovery using SSDP
38 | # ss.dependency 'AFNetworking', '~> 2.5.0' # HTTP Client
39 | # end
40 |
41 | # s.subspec 'Core' do |ss|
42 | # ss.source_files = 'Source/{HTTP Client Session Managers and Serializers,Logging,Management,Parsers,UPnP Objects}/*.swift', 'Source/SSDP/Discovery Adapter/*.swift'
43 | # ss.dependency 'UPnAtom/SSDP Explorer'
44 | # ss.dependency 'Ono', '~> 1.2.0' # XML parsing
45 | # ss.dependency 'GCDWebServer', '~> 3.2.2' # UPnP event notification handling
46 | # end
47 |
48 | # s.subspec 'AV Profile' do |ss|
49 | # ss.source_files = 'Source/AV Profile/**/*.swift'
50 | # ss.dependency 'UPnAtom/Core'
51 | # end
52 |
53 | # s.subspec 'CocoaSSDP Support' do |ss|
54 | # ss.source_files = 'Source/CocoaSSDP Support/*.swift'
55 | # ss.dependency 'UPnAtom/Core'
56 | # ss.dependency 'CocoaSSDP', '~> 0.1.0'
57 | # end
58 | end
59 |
--------------------------------------------------------------------------------
/Source/Parsers/SAXXMLParserElementObservation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SAXXMLParserElementObservation.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | public class SAXXMLParserElementObservation {
27 | // internal
28 | private(set) var didStartParsingElement: ((elementName: String, attributeDict: [NSObject : AnyObject]!) -> Void)? // TODO: Should ideally be a constant, see Github issue #10
29 | let didEndParsingElement: ((elementName: String) -> Void)?
30 | let foundInnerText: ((elementName: String, text: String) -> Void)?
31 | let elementPath: [String]
32 | private(set) var innerText: String?
33 |
34 | public init(elementPath: [String], didStartParsingElement: ((elementName: String, attributeDict: [NSObject : AnyObject]!) -> Void)?, didEndParsingElement: ((elementName: String) -> Void)?, foundInnerText: ((elementName: String, text: String) -> Void)?) {
35 | self.elementPath = elementPath
36 |
37 | self.didEndParsingElement = didEndParsingElement
38 | self.foundInnerText = foundInnerText
39 |
40 | self.didStartParsingElement = {[unowned self] (elementName: String, attributeDict: [NSObject : AnyObject]!) -> Void in
41 | // reset _innerText at the start of the element parse
42 | self.innerText = nil
43 |
44 | if let didStartParsingElement = didStartParsingElement {
45 | didStartParsingElement(elementName: elementName, attributeDict: attributeDict)
46 | }
47 | }
48 | }
49 |
50 | func appendInnerText(innerText: String?) {
51 | if self.innerText == nil {
52 | self.innerText = ""
53 | }
54 |
55 | if let innerText = innerText {
56 | self.innerText! += innerText
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | #import "AppDelegate.h"
25 |
26 | @interface AppDelegate ()
27 |
28 | @end
29 |
30 | @implementation AppDelegate
31 |
32 |
33 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
34 |
35 | // Override point for customization after application launch.
36 | return YES;
37 | }
38 |
39 | - (void)applicationWillResignActive:(UIApplication *)application {
40 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
41 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
42 | }
43 |
44 | - (void)applicationDidEnterBackground:(UIApplication *)application {
45 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
46 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
47 | }
48 |
49 | - (void)applicationWillEnterForeground:(UIApplication *)application {
50 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
51 | }
52 |
53 | - (void)applicationDidBecomeActive:(UIApplication *)application {
54 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
55 | }
56 |
57 | - (void)applicationWillTerminate:(UIApplication *)application {
58 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
59 | }
60 |
61 | @end
62 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode7.3
3 | env:
4 | global:
5 | - LC_CTYPE=en_US.UTF-8
6 | - LANG=en_US.UTF-8
7 | - OBJC_IOS_EXAMPLE_DIR="Examples/ControlPointDemo_ObjC"
8 | - SWIFT_IOS_EXAMPLE_DIR="Examples/ControlPointDemo_Swift"
9 | - SWIFT_MACOS_EXAMPLE_DIR="Examples/ControlPointDemoMacOS_Swift"
10 | - WORKSPACE=ControlPointDemo.xcworkspace
11 | - IOS_SDK=iphonesimulator9.3
12 | - OSX_SDK=macosx10.11
13 | - TVOS_SDK=appletvsimulator9.2
14 | - WATCHOS_SDK=watchsimulator2.2
15 | - EXAMPLE_SCHEME="ControlPointDemo"
16 | matrix:
17 | - DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="YES"
18 | - DESTINATION="OS=8.2,name=iPhone 5" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO"
19 | - DESTINATION="OS=8.3,name=iPhone 5S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO"
20 | - DESTINATION="OS=8.4,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO"
21 | - DESTINATION="OS=9.0,name=iPhone 6" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO"
22 | - DESTINATION="OS=9.1,name=iPhone 6 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO"
23 | - DESTINATION="OS=9.2,name=iPhone 6S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO"
24 | - DESTINATION="OS=9.3,name=iPhone 6S Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO"
25 | - DESTINATION="arch=x86_64" SCHEME="$OSX_FRAMEWORK_SCHEME" SDK="$OSX_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO"
26 | script:
27 | - set -o pipefail
28 | - xcodebuild -version
29 | - xcodebuild -showsdks
30 | - pod repo update
31 |
32 | # Build Example in Debug if specified
33 | - if [ $BUILD_EXAMPLE == "YES" ] && [ $SDK == $IOS_SDK ]; then
34 | pod install --project-directory="$OBJC_IOS_EXAMPLE_DIR";
35 | xcodebuild -workspace "$OBJC_IOS_EXAMPLE_DIR/$WORKSPACE" -scheme "$EXAMPLE_SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
36 | fi
37 |
38 | # Build Example in Debug if specified
39 | - if [ $BUILD_EXAMPLE == "YES" ] && [ $SDK == $IOS_SDK ]; then
40 | pod install --project-directory="$SWIFT_IOS_EXAMPLE_DIR";
41 | xcodebuild -workspace "$SWIFT_IOS_EXAMPLE_DIR/$WORKSPACE" -scheme "$EXAMPLE_SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
42 | fi
43 |
44 | # Build Example in Debug if specified
45 | - if [ $BUILD_EXAMPLE == "YES" ] && [ $SDK == $OSX_SDK ]; then
46 | pod install --project-directory="$SWIFT_MACOS_EXAMPLE_DIR";
47 | xcodebuild -workspace "$SWIFT_MACOS_EXAMPLE_DIR/$WORKSPACE" -scheme "$EXAMPLE_SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
48 | fi
49 |
50 | # Run `pod lib lint` if specified
51 | - if [ $POD_LINT == "YES" ]; then
52 | pod lib lint --verbose --allow-warnings;
53 | fi
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import UIKit
25 |
26 | @UIApplicationMain
27 | class AppDelegate: UIResponder, UIApplicationDelegate {
28 |
29 | var window: UIWindow?
30 |
31 |
32 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
33 | // Override point for customization after application launch.
34 | return true
35 | }
36 |
37 | func applicationWillResignActive(application: UIApplication) {
38 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
39 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
40 | }
41 |
42 | func applicationDidEnterBackground(application: UIApplication) {
43 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
44 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
45 | }
46 |
47 | func applicationWillEnterForeground(application: UIApplication) {
48 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
49 | }
50 |
51 | func applicationDidBecomeActive(application: UIApplication) {
52 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
53 | }
54 |
55 | func applicationWillTerminate(application: UIApplication) {
56 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
57 | }
58 |
59 |
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/Source/UPnP Objects/UPnPArchivable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UPnPArchivable.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | public class UPnPArchivable: NSObject, NSCoding {
27 | public let usn: String
28 | public let descriptionURL: NSURL
29 |
30 | init(usn: String, descriptionURL: NSURL) {
31 | self.usn = usn
32 | self.descriptionURL = descriptionURL
33 | }
34 |
35 | required public init?(coder decoder: NSCoder) {
36 | self.usn = decoder.decodeObjectForKey("usn") as! String
37 | self.descriptionURL = decoder.decodeObjectForKey("descriptionURL") as! NSURL
38 | }
39 |
40 | public func encodeWithCoder(coder: NSCoder) {
41 | coder.encodeObject(usn, forKey: "usn")
42 | coder.encodeObject(descriptionURL, forKey: "descriptionURL")
43 | }
44 | }
45 |
46 | extension AbstractUPnP {
47 | public func archivable() -> UPnPArchivable {
48 | return UPnPArchivable(usn: usn.rawValue, descriptionURL: descriptionURL)
49 | }
50 | }
51 |
52 | public class UPnPArchivableAnnex: UPnPArchivable {
53 | /// Use the custom metadata dictionary to re-populate any missing data fields from a custom device or service subclass. While it's not enforced by the compiler, the contents of the meta data must conform to the NSCoding protocol in order to be archivable. Avoided using Swift generics in order to allow compatability with Obj-C.
54 | public let customMetadata: [String: AnyObject]
55 |
56 | init(usn: String, descriptionURL: NSURL, customMetadata: [String: AnyObject]) {
57 | self.customMetadata = customMetadata
58 | super.init(usn: usn, descriptionURL: descriptionURL)
59 | }
60 |
61 | required public init?(coder decoder: NSCoder) {
62 | self.customMetadata = decoder.decodeObjectForKey("customMetadata") as! [String: AnyObject]
63 | super.init(coder: decoder)
64 | }
65 |
66 | public override func encodeWithCoder(coder: NSCoder) {
67 | super.encodeWithCoder(coder)
68 | coder.encodeObject(customMetadata, forKey: "customMetadata")
69 | }
70 | }
71 |
72 | extension AbstractUPnP {
73 | public func archivable(customMetadata customMetadata: [String: AnyObject]) -> UPnPArchivableAnnex {
74 | return UPnPArchivableAnnex(usn: usn.rawValue, descriptionURL: descriptionURL, customMetadata: customMetadata)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | [](http://cocoapods.org/?q=UPnAtom)
3 | [](https://github.com/master-nevi/UPnAtom/blob/master/UPnAtom.podspec)
4 | [](https://github.com/master-nevi/UPnAtom/blob/master/LICENSE)
5 | [](https://travis-ci.org/master-nevi/UPnAtom)
6 |
7 | An open source Universal Plug and Play library with a focus on media streaming coordination using the UPnP A/V profile; written in Swift but for both Objective-C and Swift apps. Supports only iOS 8 and higher due to iOS 7's limitation of not supporting dynamic libraries via Clang module.
8 |
9 | ### Requirements:
10 | * iOS 8.0+
11 | * OSX 10.9+
12 | * Xcode 7.2
13 |
14 | ### Install:
15 | Add following to Podfile:
16 | ```ruby
17 | pod 'UPnAtom'
18 | ```
19 |
20 | ### Usage:
21 | ###### Objective-C
22 | ```objective-c
23 | @import UPnAtom;
24 | ```
25 |
26 | ###### Swift
27 | ```swift
28 | import UPnAtom
29 | ```
30 |
31 | ###### More documentation is on the way.
32 | For now, it is highly recommended you check out the [example projects](https://github.com/master-nevi/UPnAtom/tree/master/Examples). They are exactly the same app however one is in Swift, and the other is in Objective-C. They demonstrate almost all of the library's features minus the ability to add your own UPnP service/device classes. If you create your own service/device classes simply register them following [UPnAtom.swift](https://github.com/master-nevi/UPnAtom/blob/master/Source/UPnAtom.swift) as an example.
33 |
34 | Note: On iOS, transport security has blocked cleartext HTTP (http://) resource loads since it is insecure. Since many, if not most, UPnP devices serve resources over http, temporary exceptions can be configured via your app's Info.plist file. Remove this restriction at your own risk.
35 |
36 | ### Milestones:
37 | * [x] Usable in both Swift and Objective-C projects via CocoaPod framework
38 | * [x] Create your own service and device object via class registration
39 | * [x] UPnP Version 1 Compliance
40 | * [x] Ability to archive UPnP objects after initial discovery and persist somewhere via NSCoder/NSCoding
41 | * [x] OSX 10.9+ support
42 | * [ ] Swift 2.0
43 | * [x] In-house implementation of SSDP discovery
44 | * [x] A/V Profile Feature parity with upnpx library
45 | * [ ] Documentation (Until then please check out the [example projects](https://github.com/master-nevi/UPnAtom/tree/master/Examples))
46 | * [ ] UPnP Version 2 Compliance
47 |
48 | ### Tested Support On:
49 | ###### UPnP Servers:
50 | * [Kodi](http://kodi.tv/) - Open Source Home Theatre Software (aka XBMC) - [How to enable](http://kodi.wiki/view/UPnP/Server)
51 | * [Universal Media Server](http://www.universalmediaserver.com/) (fork of PS3 Media Server)
52 |
53 | ###### UPnP Clients:
54 | * [Kodi](http://kodi.tv/) - Open Source Home Theatre Software (aka XBMC) - [How to enable](http://kodi.wiki/view/UPnP/Client)
55 | * [Sony Bravia TV's with DLNA support](http://esupport.sony.com/p/support-info.pl?info_id=884&template_id=1®ion_id=8)
56 |
57 | ### Contribute:
58 | Currently I'm only taking feature requests, bugs, and bug fixes via [Github issue](https://github.com/master-nevi/UPnAtom/issues). Sorry no pull requests for features or major changes until the library is mature enough.
59 |
60 | - If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/).
61 | - If you **found a bug**, open an issue.
62 | - If you **have a feature request**, open an issue.
63 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo.xcodeproj/xcshareddata/xcschemes/ControlPointDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo.xcodeproj/xcshareddata/xcschemes/ControlPointDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemoMacOS_Swift/ControlPointDemo.xcodeproj/xcshareddata/xcschemes/ControlPointDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Source/UPnP Objects/AbstractUPnP.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AbstractUPnP.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | /// TODO: For now rooting to NSObject to expose to Objective-C, see Github issue #16
27 | public class AbstractUPnP: NSObject {
28 | public var uuid: String {
29 | return usn.uuid
30 | }
31 | public var urn: String {
32 | return usn.urn! // checked for nil during init
33 | }
34 | public let usn: UniqueServiceName
35 | public let descriptionURL: NSURL
36 | public var baseURL: NSURL! {
37 | return NSURL(string: "/", relativeToURL: descriptionURL)?.absoluteURL
38 | }
39 |
40 | required public init?(usn: UniqueServiceName, descriptionURL: NSURL, descriptionXML: NSData) {
41 | self.usn = usn
42 | self.descriptionURL = descriptionURL
43 | super.init()
44 |
45 | // only deal with UPnP object's with URN's for now, i.e. is either a device or service
46 | guard let urn = usn.urn where !urn.isEmpty else {
47 | return nil
48 | }
49 | }
50 | }
51 |
52 | public func ==(lhs: AbstractUPnP, rhs: AbstractUPnP) -> Bool {
53 | return lhs.usn == rhs.usn
54 | }
55 |
56 | extension AbstractUPnP {
57 | override public var hashValue: Int {
58 | return usn.hashValue
59 | }
60 |
61 | /// Because self is rooted to NSObject, for now, usage as a key in a dictionary will be treated as a key within an NSDictionary; which requires the overriding the methods hash and isEqual, see Github issue #16
62 | override public var hash: Int {
63 | return hashValue
64 | }
65 |
66 | /// Because self is rooted to NSObject, for now, usage as a key in a dictionary will be treated as a key within an NSDictionary; which requires the overriding the methods hash and isEqual, see Github issue #16
67 | override public func isEqual(object: AnyObject?) -> Bool {
68 | if let other = object as? AbstractUPnP {
69 | return self == other
70 | }
71 | return false
72 | }
73 | }
74 |
75 | extension AbstractUPnP: ExtendedPrintable {
76 | #if os(iOS)
77 | public var className: String { return "\(self.dynamicType)" }
78 | #elseif os(OSX) // NSObject.className actually exists on OSX! Who knew.
79 | override public var className: String { return "\(self.dynamicType)" }
80 | #endif
81 | override public var description: String {
82 | var properties = PropertyPrinter()
83 | properties.add("uuid", property: uuid)
84 | properties.add("urn", property: urn)
85 | properties.add("usn", property: usn.description)
86 | properties.add("descriptionURL", property: descriptionURL.absoluteString)
87 | properties.add("baseURL", property: baseURL.absoluteString)
88 | return properties.description
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Source/UPnAtom.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UPnAtom.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | /// TODO: For now rooting to NSObject to expose to Objective-C, see Github issue #16
27 | public class UPnAtom: NSObject {
28 | // public
29 | public static let sharedInstance = UPnAtom()
30 | public let upnpRegistry: UPnPRegistry
31 | public var ssdpTypes: Set {
32 | get { return ssdpDiscoveryAdapter.rawSSDPTypes }
33 | set { ssdpDiscoveryAdapter.rawSSDPTypes = newValue }
34 | }
35 |
36 | // internal
37 | unowned let ssdpDiscoveryAdapter: SSDPDiscoveryAdapter
38 |
39 | override init() {
40 | // configure discovery adapter
41 | let adapterClass = UPnAtom.ssdpDiscoveryAdapterClass()
42 | let adapter = adapterClass.init()
43 | ssdpDiscoveryAdapter = adapter
44 |
45 | // configure UPNP registry
46 | upnpRegistry = UPnPRegistry(ssdpDiscoveryAdapter: ssdpDiscoveryAdapter)
47 | for (upnpClass, urn) in UPnAtom.upnpClasses() {
48 | self.upnpRegistry.register(upnpClass: upnpClass, forURN: urn)
49 | }
50 | }
51 |
52 | deinit {
53 | ssdpDiscoveryAdapter.stop()
54 | }
55 |
56 | public func ssdpDiscoveryRunning() -> Bool {
57 | return ssdpDiscoveryAdapter.running
58 | }
59 |
60 | public func startSSDPDiscovery() {
61 | ssdpDiscoveryAdapter.start()
62 | }
63 |
64 | public func stopSSDPDiscovery() {
65 | ssdpDiscoveryAdapter.stop()
66 | }
67 |
68 | public func restartSSDPDiscovery() {
69 | ssdpDiscoveryAdapter.restart()
70 | }
71 |
72 | /// Override to use a different SSDP adapter if another SSDP system is preferred over CocoaSSDP
73 | class func ssdpDiscoveryAdapterClass() -> AbstractSSDPDiscoveryAdapter.Type {
74 | return SSDPExplorerDiscoveryAdapter.self
75 | }
76 |
77 | /// Override to use a different default set of UPnP classes. Alternatively, registrations can be replaced, see UPnAtom.upnpRegistry.register()
78 | class func upnpClasses() -> [(upnpClass: AbstractUPnP.Type, forURN: String)] {
79 | return [
80 | (upnpClass: MediaRenderer1Device.self, forURN: "urn:schemas-upnp-org:device:MediaRenderer:1"),
81 | (upnpClass: MediaServer1Device.self, forURN: "urn:schemas-upnp-org:device:MediaServer:1"),
82 | (upnpClass: AVTransport1Service.self, forURN: "urn:schemas-upnp-org:service:AVTransport:1"),
83 | (upnpClass: ConnectionManager1Service.self, forURN: "urn:schemas-upnp-org:service:ConnectionManager:1"),
84 | (upnpClass: ContentDirectory1Service.self, forURN: "urn:schemas-upnp-org:service:ContentDirectory:1"),
85 | (upnpClass: RenderingControl1Service.self, forURN: "urn:schemas-upnp-org:service:RenderingControl:1")
86 | ]
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemoMacOS_Swift/ControlPointDemo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // ControlPointDemo
4 | //
5 | // Created by David Robles on 8/21/16.
6 | // Copyright © 2016 David Robles. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import UPnAtom
11 |
12 | class ViewController: NSViewController {
13 |
14 | override func viewDidAppear() {
15 | super.viewDidAppear()
16 |
17 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.deviceWasAdded(_:)), name: UPnPRegistry.UPnPDeviceAddedNotification(), object: nil)
18 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.deviceWasRemoved(_:)), name: UPnPRegistry.UPnPDeviceRemovedNotification(), object: nil)
19 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.serviceWasAdded(_:)), name: UPnPRegistry.UPnPServiceAddedNotification(), object: nil)
20 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.serviceWasRemoved(_:)), name: UPnPRegistry.UPnPServiceRemovedNotification(), object: nil)
21 |
22 | UPnAtom.sharedInstance.ssdpTypes = [
23 | SSDPTypeConstant.All.rawValue,
24 | SSDPTypeConstant.MediaServerDevice1.rawValue,
25 | SSDPTypeConstant.MediaRendererDevice1.rawValue,
26 | SSDPTypeConstant.ContentDirectory1Service.rawValue,
27 | SSDPTypeConstant.ConnectionManager1Service.rawValue,
28 | SSDPTypeConstant.RenderingControl1Service.rawValue,
29 | SSDPTypeConstant.AVTransport1Service.rawValue
30 | ]
31 |
32 | if !UPnAtom.sharedInstance.ssdpDiscoveryRunning() {
33 | UPnAtom.sharedInstance.startSSDPDiscovery()
34 | }
35 | }
36 |
37 | override func viewDidDisappear() {
38 | NSNotificationCenter.defaultCenter().removeObserver(self, name: UPnPRegistry.UPnPDeviceAddedNotification(), object: nil)
39 | NSNotificationCenter.defaultCenter().removeObserver(self, name: UPnPRegistry.UPnPDeviceRemovedNotification(), object: nil)
40 | NSNotificationCenter.defaultCenter().removeObserver(self, name: UPnPRegistry.UPnPServiceAddedNotification(), object: nil)
41 | NSNotificationCenter.defaultCenter().removeObserver(self, name: UPnPRegistry.UPnPServiceRemovedNotification(), object: nil)
42 |
43 | super.viewDidDisappear()
44 | }
45 |
46 | @objc private func deviceWasAdded(notification: NSNotification) {
47 | if let upnpDevice = notification.userInfo?[UPnPRegistry.UPnPDeviceKey()] as? AbstractUPnPDevice {
48 | print("Added device: \(upnpDevice.className) - \(upnpDevice.friendlyName)")
49 | }
50 | }
51 |
52 | @objc private func deviceWasRemoved(notification: NSNotification) {
53 | if let upnpDevice = notification.userInfo?[UPnPRegistry.UPnPDeviceKey()] as? AbstractUPnPDevice {
54 | print("Removed device: \(upnpDevice.className) - \(upnpDevice.friendlyName)")
55 | }
56 | }
57 |
58 | @objc private func serviceWasAdded(notification: NSNotification) {
59 | if let upnpService = notification.userInfo?[UPnPRegistry.UPnPServiceKey()] as? AbstractUPnPService {
60 | print("Added service: \(upnpService.className) - \(upnpService.descriptionURL)")
61 |
62 | if upnpService is AVTransport1Service {
63 | upnpService.addEventObserver(NSOperationQueue.currentQueue(), callBackBlock: { (event: UPnPEvent) -> Void in
64 | if let avTransportEvent = event as? AVTransport1Event {
65 | print("\(event.service?.className) Event: \(avTransportEvent.instanceState)")
66 | }
67 | })
68 | }
69 | }
70 | }
71 |
72 | @objc private func serviceWasRemoved(notification: NSNotification) {
73 | if let upnpService = notification.userInfo?[UPnPRegistry.UPnPServiceKey()] as? AbstractUPnPService {
74 | print("Removed service: \(upnpService.className) - \(upnpService.descriptionURL)")
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Source/Logging/Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logger.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | struct LogFlag : OptionSetType {
27 | let rawValue: UInt
28 |
29 | static let Error = LogFlag(rawValue: 1 << 0) // 0...00001
30 | static let Warning = LogFlag(rawValue: 1 << 1) // 0...00010
31 | static let Info = LogFlag(rawValue: 1 << 2) // 0...00100
32 | static let Debug = LogFlag(rawValue: 1 << 3) // 0...01000
33 | static let Verbose = LogFlag(rawValue: 1 << 4) // 0...10000
34 | }
35 |
36 | /// Debug levels
37 | extension LogFlag {
38 | static let OffLevel: LogFlag = []
39 | static let ErrorLevel: LogFlag = [.Error]
40 | static let WarningLevel: LogFlag = [.Error, .Warning]
41 | static let InfoLevel: LogFlag = [.Error, .Warning, .Info]
42 | static let DebugLevel: LogFlag = [.Error, .Warning, .Info, .Debug]
43 | static let VerboseLevel: LogFlag = [.Error, .Warning, .Info, .Debug, .Verbose]
44 |
45 | }
46 |
47 | let defaultLogLevel = LogFlag.DebugLevel
48 |
49 | var logLevel = defaultLogLevel
50 |
51 | func resetDefaultDebugLevel() {
52 | logLevel = defaultLogLevel
53 | }
54 |
55 | func Log(isAsynchronous: Bool, level: LogFlag, flag: LogFlag, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, @autoclosure string: () -> String) {
56 | if level.contains(flag) {
57 | print(string())
58 | }
59 | }
60 |
61 | func LogDebug(@autoclosure logText: () -> String, level: LogFlag = logLevel, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, asynchronous async: Bool = true) {
62 | Log(async, level: level, flag: .Debug, file: file, function: function, line: line, string: logText)
63 | }
64 |
65 | func LogInfo(@autoclosure logText: () -> String, level: LogFlag = logLevel, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, asynchronous async: Bool = true) {
66 | Log(async, level: level, flag: .Info, file: file, function: function, line: line, string: logText)
67 | }
68 |
69 | func LogWarn(@autoclosure logText: () -> String, level: LogFlag = logLevel, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, asynchronous async: Bool = true) {
70 | Log(async, level: level, flag: .Warning, file: file, function: function, line: line, string: logText)
71 | }
72 |
73 | func LogVerbose(@autoclosure logText: () -> String, level: LogFlag = logLevel, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, asynchronous async: Bool = true) {
74 | Log(async, level: level, flag: .Verbose, file: file, function: function, line: line, string: logText)
75 | }
76 |
77 | func LogError(@autoclosure logText: () -> String, level: LogFlag = logLevel, file: StaticString = __FILE__, function: StaticString = __FUNCTION__, line: UInt = __LINE__, asynchronous async: Bool = false) {
78 | Log(async, level: level, flag: .Error, file: file, function: function, line: line, string: logText)
79 | }
80 |
--------------------------------------------------------------------------------
/Source/UPnP Foundation/SSDPType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SSDPType.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | public enum SSDPTypeConstant: String {
25 | // General
26 | case All = "ssdp:all"
27 | case RootDevice = "upnp:rootdevice"
28 |
29 | // UPnP A/V profile
30 | case MediaServerDevice1 = "urn:schemas-upnp-org:device:MediaServer:1"
31 | case MediaRendererDevice1 = "urn:schemas-upnp-org:device:MediaRenderer:1"
32 | case ContentDirectory1Service = "urn:schemas-upnp-org:service:ContentDirectory:1"
33 | case ConnectionManager1Service = "urn:schemas-upnp-org:service:ConnectionManager:1"
34 | case RenderingControl1Service = "urn:schemas-upnp-org:service:RenderingControl:1"
35 | case AVTransport1Service = "urn:schemas-upnp-org:service:AVTransport:1"
36 | }
37 |
38 | enum SSDPType: RawRepresentable {
39 | case All
40 | case RootDevice
41 | case UUID(String)
42 | case Device(String)
43 | case Service(String)
44 |
45 | typealias RawValue = String
46 |
47 | init?(rawValue: RawValue) {
48 | if rawValue == "ssdp:all" {
49 | self = .All
50 | } else if rawValue == "upnp:rootdevice" {
51 | self = .RootDevice
52 | } else if rawValue.rangeOfString("uuid:") != nil {
53 | self = .UUID(rawValue)
54 | } else if rawValue.rangeOfString(":device:") != nil {
55 | self = .Device(rawValue)
56 | } else if rawValue.rangeOfString(":service:") != nil {
57 | self = .Service(rawValue)
58 | } else {
59 | return nil
60 | }
61 | }
62 |
63 | init?(typeConstant: SSDPTypeConstant) {
64 | self.init(rawValue: typeConstant.rawValue)
65 | }
66 |
67 | var rawValue: RawValue {
68 | switch self {
69 | case .All:
70 | return "ssdp:all"
71 | case .RootDevice:
72 | return "upnp:rootdevice"
73 | case .UUID(let rawValue):
74 | return rawValue
75 | case .Device(let rawValue):
76 | return rawValue
77 | case .Service(let rawValue):
78 | return rawValue
79 | }
80 | }
81 | }
82 |
83 | extension SSDPType: CustomStringConvertible {
84 | var description: String {
85 | return self.rawValue
86 | }
87 | }
88 |
89 | extension SSDPType: Hashable {
90 | var hashValue: Int {
91 | return self.rawValue.hashValue
92 | }
93 | }
94 |
95 | func ==(lhs: SSDPType, rhs: SSDPType) -> Bool {
96 | switch (lhs, rhs) {
97 | case (.All, .All):
98 | return true
99 | case (.RootDevice, .RootDevice):
100 | return true
101 | case (.UUID(let lhsRawValue), .UUID(let rhsRawValue)):
102 | return lhsRawValue == rhsRawValue
103 | case (.Device(let lhsRawValue), .Device(let rhsRawValue)):
104 | return lhsRawValue == rhsRawValue
105 | case (.Service(let lhsRawValue), .Service(let rhsRawValue)):
106 | return lhsRawValue == rhsRawValue
107 | default:
108 | return false
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/FolderViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderViewController.swift
3 | // ControlPointDemo
4 | //
5 | // Created by David Robles on 3/14/15.
6 | // Copyright (c) 2015 David Robles. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import UPnAtom
11 |
12 | class FolderViewController: UIViewController {
13 | @IBOutlet private weak var _tableView: UITableView!
14 | private var _playlist = [ContentDirectory1Object]()
15 | private var _mediaServer: MediaServer1Device!
16 | private var _contentDirectoryID: String!
17 |
18 | func configure(mediaServer mediaServer: MediaServer1Device, title: String, contentDirectoryID: String) {
19 | _mediaServer = mediaServer
20 | _contentDirectoryID = contentDirectoryID
21 | self.title = title
22 | }
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 |
27 | _mediaServer?.contentDirectoryService?.getSortCapabilities({ (sortCapabilities: String?) -> Void in
28 | var sortCapabilities = sortCapabilities != nil ? sortCapabilities! : ""
29 | if sortCapabilities.rangeOfString("dc:title") != nil {
30 | sortCapabilities = "+dc:title"
31 | }
32 |
33 | self._mediaServer?.contentDirectoryService?.browse(objectID: self._contentDirectoryID, browseFlag: "BrowseDirectChildren", filter: "*", startingIndex: "0", requestedCount: "0", sortCriteria: sortCapabilities, success: { (result: [ContentDirectory1Object], numberReturned: Int, totalMatches: Int, updateID: String?) -> Void in
34 | self._playlist = result
35 | self._tableView.reloadData()
36 | }, failure: { (error: NSError) -> Void in
37 | print("Failed to browse content directory: \(error)")
38 | })
39 | }, failure: { (error: NSError) -> Void in
40 | print("Failed to get sort capabilities: \(error)")
41 | })
42 |
43 | let viewWidth = self.navigationController!.view.frame.size.width
44 | let titleLabel = UILabel(frame: CGRect(x: 0.0, y: 11.0, width: viewWidth - (viewWidth * 0.2), height: 21.0))
45 | titleLabel.font = UIFont(name: "Helvetica", size: 18)
46 | titleLabel.backgroundColor = UIColor.clearColor()
47 | titleLabel.textColor = UIColor.blackColor()
48 | titleLabel.textAlignment = .Left
49 | titleLabel.text = Player.sharedInstance.mediaRenderer == nil ? "No Renderer Selected" : Player.sharedInstance.mediaRenderer?.friendlyName
50 | let barButton = UIBarButtonItem(customView: titleLabel)
51 | self.toolbarItems = [Player.sharedInstance.playPauseButton, Player.sharedInstance.stopButton, barButton]
52 |
53 | self.navigationController?.toolbarHidden = false
54 | }
55 | }
56 |
57 | extension FolderViewController: UITableViewDataSource {
58 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
59 | return _playlist.count
60 | }
61 |
62 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
63 | let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("DefaultCell") as UITableViewCell!
64 | let item = _playlist[indexPath.row]
65 | cell.textLabel?.text = item.title
66 | cell.accessoryType = item is ContentDirectory1Container ? .DisclosureIndicator : .None
67 |
68 | return cell
69 | }
70 | }
71 |
72 | extension FolderViewController: UITableViewDelegate {
73 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
74 | let item = _playlist[indexPath.row]
75 | if let containerItem = item as? ContentDirectory1Container {
76 | let targetViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("FolderViewControllerScene") as! FolderViewController
77 | targetViewController.configure(mediaServer: _mediaServer, title: containerItem.title, contentDirectoryID: containerItem.objectID)
78 | self.navigationController?.pushViewController(targetViewController, animated: true)
79 | }
80 | else {
81 | Player.sharedInstance.startPlayback(_playlist, position: indexPath.row)
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Source/SSDP/Discovery Adapter/SSDPExplorerDiscoveryAdapter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SSDPExplorerDiscoveryAdapter.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | class SSDPExplorerDiscoveryAdapter: AbstractSSDPDiscoveryAdapter {
27 | lazy private var _ssdpExplorer = SSDPExplorer()
28 | /// Never reading without writing so a serial queue is adequate
29 | private let _serialSSDPDiscoveryQueue = dispatch_queue_create("com.upnatom.ssdp-explorer-discovery-adapter.ssdp-discovery-queue", DISPATCH_QUEUE_SERIAL)
30 | /// Must be accessed and updated within dispatch_sync() or dispatch_async() to the serial queue
31 | private var _ssdpDiscoveries = [UniqueServiceName: SSDPDiscovery]()
32 |
33 | required init() {
34 | super.init()
35 |
36 | _ssdpExplorer.delegate = self
37 | }
38 |
39 | override func start() {
40 | super.start()
41 |
42 | var types = [SSDPType]() // TODO: Should ideally be a Set, see Github issue #13
43 | for rawSSDPType in rawSSDPTypes {
44 | if let ssdpType = SSDPType(rawValue: rawSSDPType) {
45 | types.append(ssdpType)
46 | }
47 | }
48 | if let resultError = _ssdpExplorer.startExploring(forTypes: types).error {
49 | failed🔰()
50 | notifyDelegate(ofFailure: resultError)
51 | }
52 | }
53 |
54 | override func stop() {
55 | _ssdpExplorer.stopExploring()
56 |
57 | dispatch_async(_serialSSDPDiscoveryQueue, { () -> Void in
58 | self._ssdpDiscoveries.removeAll(keepCapacity: false)
59 |
60 | self.notifyDelegate(ofDiscoveries: Array(self._ssdpDiscoveries.values))
61 | })
62 |
63 | super.stop()
64 | }
65 |
66 | private func notifyDelegate(ofFailure error: NSError) {
67 | dispatch_async(delegateQueue, { () -> Void in
68 | self.delegate?.ssdpDiscoveryAdapter(self, didFailWithError: error)
69 | })
70 | }
71 |
72 | private func notifyDelegate(ofDiscoveries discoveries: [SSDPDiscovery]) {
73 | dispatch_async(delegateQueue, { () -> Void in
74 | self.delegate?.ssdpDiscoveryAdapter(self, didUpdateSSDPDiscoveries: discoveries)
75 | })
76 | }
77 | }
78 |
79 | extension SSDPExplorerDiscoveryAdapter: SSDPExplorerDelegate {
80 | func ssdpExplorer(explorer: SSDPExplorer, didMakeDiscovery discovery: SSDPDiscovery) {
81 | dispatch_async(_serialSSDPDiscoveryQueue, { () -> Void in
82 | self._ssdpDiscoveries[discovery.usn] = discovery
83 |
84 | self.notifyDelegate(ofDiscoveries: Array(self._ssdpDiscoveries.values))
85 | })
86 | }
87 |
88 | func ssdpExplorer(explorer: SSDPExplorer, didRemoveDiscovery discovery: SSDPDiscovery) {
89 | dispatch_async(_serialSSDPDiscoveryQueue, { () -> Void in
90 | if let discovery = self._ssdpDiscoveries[discovery.usn] {
91 | self._ssdpDiscoveries.removeValueForKey(discovery.usn)
92 |
93 | self.notifyDelegate(ofDiscoveries: Array(self._ssdpDiscoveries.values))
94 | }
95 | })
96 | }
97 |
98 | func ssdpExplorer(explorer: SSDPExplorer, didFailWithError error: NSError) {
99 | failed🔰()
100 | notifyDelegate(ofFailure: error)
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Source/UPnP Foundation/UniqueServiceName.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UniqueServiceName.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | /// TODO: For now rooting to NSObject to expose to Objective-C, see Github issue #16
27 | public class UniqueServiceName: NSObject, RawRepresentable {
28 | public let rawValue: RawValue
29 | public let uuid: String
30 | public let urn: String?
31 | public let rootDevice: Bool
32 |
33 | public typealias RawValue = String
34 |
35 | public required init?(rawValue: RawValue) {
36 | self.rawValue = rawValue
37 |
38 | // all forms of usn should contain a uuid, otherwise it's invalid and nil will be returned
39 | guard let uuid = UniqueServiceName.uuid(usn: rawValue) where !uuid.isEmpty else {
40 | /// TODO: Remove default initializations to simply return nil, see Github issue #11
41 | self.uuid = ""
42 | self.rootDevice = false
43 | self.urn = nil
44 | super.init()
45 | return nil
46 | }
47 |
48 | self.uuid = uuid
49 |
50 | rootDevice = UniqueServiceName.isRootDevice(usn: rawValue)
51 | urn = UniqueServiceName.urn(usn: rawValue)
52 | super.init()
53 | }
54 |
55 | convenience public init?(uuid: String, urn: String) {
56 | self.init(rawValue: "\(uuid)::\(urn)")
57 | }
58 |
59 | convenience public init?(uuid: String, rootDevice: Bool) {
60 | let rawValue = rootDevice ? "\(uuid)::upnp:rootdevice" : "\(uuid)"
61 | self.init(rawValue: rawValue)
62 | }
63 |
64 | class func uuid(usn usn: String) -> String? {
65 | let usnComponents = usn.componentsSeparatedByString("::")
66 | return (usnComponents.count >= 1 && usnComponents[0].rangeOfString("uuid:") != nil) ? usnComponents[0] : nil
67 | }
68 |
69 | class func urn(usn usn: String) -> String? {
70 | let usnComponents = usn.componentsSeparatedByString("::")
71 | return (usnComponents.count >= 2 && usnComponents[1].rangeOfString("urn:") != nil) ? usnComponents[1] : nil
72 | }
73 |
74 | class func isRootDevice(usn usn: String) -> Bool {
75 | let usnComponents = usn.componentsSeparatedByString("::")
76 | return usnComponents.count >= 2 && usnComponents[1].rangeOfString("upnp:rootdevice") != nil
77 | }
78 | }
79 |
80 | extension UniqueServiceName {
81 | override public var description: String {
82 | return rawValue
83 | }
84 | }
85 |
86 | extension UniqueServiceName {
87 | override public var hashValue: Int {
88 | return rawValue.hashValue
89 | }
90 |
91 | /// Because self is rooted to NSObject, for now, usage as a key in a dictionary will be treated as a key within an NSDictionary; which requires the overriding the methods hash and isEqual, see Github issue #16
92 | override public var hash: Int {
93 | return hashValue
94 | }
95 |
96 | /// Because self is rooted to NSObject, for now, usage as a key in a dictionary will be treated as a key within an NSDictionary; which requires the overriding the methods hash and isEqual, see Github issue #16
97 | override public func isEqual(object: AnyObject?) -> Bool {
98 | if let other = object as? UniqueServiceName {
99 | return self == other
100 | }
101 | return false
102 | }
103 | }
104 |
105 | public func ==(lhs: UniqueServiceName, rhs: UniqueServiceName) -> Bool {
106 | return lhs.rawValue == rhs.rawValue
107 | }
108 |
--------------------------------------------------------------------------------
/Source/AV Profile/Events/AVTransport1Event.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AVTransport1Event.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Ono
25 |
26 | public class AVTransport1Event: UPnPEvent {
27 | public var instanceState = [String: AnyObject]()
28 |
29 | override public init(eventXML: NSData, service: AbstractUPnPService) {
30 | super.init(eventXML: eventXML, service: service)
31 |
32 | if let parsedInstanceState = AVTransport1EventParser().parse(eventXML: eventXML).value {
33 | instanceState = parsedInstanceState
34 | }
35 | }
36 | }
37 |
38 | /// for objective-c type checking
39 | extension UPnPEvent {
40 | public func isAVTransport1Event() -> Bool {
41 | return self is AVTransport1Event
42 | }
43 | }
44 |
45 | class AVTransport1EventParser: AbstractDOMXMLParser {
46 | private var _instanceState = [String: AnyObject]()
47 |
48 | override func parse(document document: ONOXMLDocument) -> EmptyResult {
49 | let result: EmptyResult = .Success
50 |
51 | // procedural vs series of nested if let's
52 | guard let lastChangeXMLString = document.firstChildWithXPath("/e:propertyset/e:property/LastChange")?.stringValue() else {
53 | return .Failure(createError("No LastChange element in UPnP service event XML"))
54 | }
55 |
56 | LogVerbose("Parsing LastChange XML:\nSTART\n\(lastChangeXMLString)\nEND")
57 |
58 | guard let lastChangeEventDocument = try? ONOXMLDocument(string: lastChangeXMLString, encoding: NSUTF8StringEncoding) else {
59 | return .Failure(createError("Unable to parse LastChange XML"))
60 | }
61 |
62 | lastChangeEventDocument.definePrefix("avt", forDefaultNamespace: "urn:schemas-upnp-org:metadata-1-0/AVT/")
63 | lastChangeEventDocument.enumerateElementsWithXPath("/avt:Event/avt:InstanceID/*", usingBlock: { [unowned self] (element: ONOXMLElement!, index: UInt, stop: UnsafeMutablePointer) -> Void in
64 | if let stateValue = element.valueForAttribute("val") as? String where !stateValue.isEmpty {
65 | if element.tag.rangeOfString("MetaData") != nil {
66 | guard let metadataDocument = try? ONOXMLDocument(string: stateValue, encoding: NSUTF8StringEncoding) else {
67 | return
68 | }
69 |
70 | LogVerbose("Parsing MetaData XML:\nSTART\n\(stateValue)\nEND")
71 |
72 | var metaData = [String: String]()
73 |
74 | metadataDocument.definePrefix("didllite", forDefaultNamespace: "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/")
75 | metadataDocument.enumerateElementsWithXPath("/didllite:DIDL-Lite/didllite:item/*", usingBlock: { (metadataElement: ONOXMLElement!, index: UInt, stop: UnsafeMutablePointer) -> Void in
76 | if let elementStringValue = metadataElement.stringValue() where !elementStringValue.isEmpty {
77 | metaData[metadataElement.tag] = elementStringValue
78 | }
79 | })
80 |
81 | self._instanceState[element.tag] = metaData
82 | } else {
83 | self._instanceState[element.tag] = stateValue
84 | }
85 | }
86 | })
87 |
88 | return result
89 | }
90 |
91 | func parse(eventXML eventXML: NSData) -> Result<[String: AnyObject]> {
92 | switch super.parse(data: eventXML) {
93 | case .Success:
94 | return .Success(_instanceState)
95 | case .Failure(let error):
96 | return .Failure(error)
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Source/CocoaSSDP Support/CocoaSSDPDiscoveryAdapter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CocoaSSDPDiscoveryAdapter.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import CocoaSSDP
25 |
26 | class CocoaSSDPDiscoveryAdapter: AbstractSSDPDiscoveryAdapter {
27 | /// Must be set before calling start()
28 | lazy private var _ssdpBrowsers: Set = {
29 | var ssdpBrowsers = Set()
30 | for type in self.rawSSDPTypes {
31 | let ssdpBrowser = SSDPServiceBrowser(serviceType: type)
32 | ssdpBrowser.delegate = self
33 | ssdpBrowsers.insert(ssdpBrowser)
34 | }
35 |
36 | return ssdpBrowsers
37 | }()
38 | /// Never reading without writing so a serial queue is adequate
39 | private let _serialSSDPDiscoveryQueue = dispatch_queue_create("com.upnatom.cocoa-ssdp-discovery-adapter.ssdp-discovery-queue", DISPATCH_QUEUE_SERIAL)
40 | /// Must be accessed and updated within dispatch_sync() or dispatch_async() to the serial queue
41 | private var _ssdpDiscoveries = [String: SSDPDiscovery]()
42 |
43 | override func start() {
44 | super.start()
45 |
46 | for ssdpBrowser in _ssdpBrowsers {
47 | ssdpBrowser.startBrowsingForServices()
48 | }
49 | }
50 |
51 | override func stop() {
52 | for ssdpBrowser in _ssdpBrowsers {
53 | ssdpBrowser.stopBrowsingForServices()
54 | }
55 |
56 | dispatch_async(_serialSSDPDiscoveryQueue, { () -> Void in
57 | self._ssdpDiscoveries.removeAll(keepCapacity: false)
58 |
59 | self.notifyDelegate(ofDiscoveries: self._ssdpDiscoveries.values.array)
60 | })
61 |
62 | super.stop()
63 | }
64 |
65 | private func notifyDelegate(ofFailure error: NSError) {
66 | dispatch_async(delegateQueue, { () -> Void in
67 | delegate?.ssdpDiscoveryAdapter(self, didFailWithError: error)
68 | })
69 | }
70 |
71 | private func notifyDelegate(ofDiscoveries discoveries: [SSDPDiscovery]) {
72 | dispatch_async(delegateQueue, { () -> Void in
73 | self.delegate?.ssdpDiscoveryAdapter(self, didUpdateSSDPDiscoveries: discoveries)
74 | })
75 | }
76 | }
77 |
78 | extension CocoaSSDPDiscoveryAdapter: SSDPServiceBrowserDelegate {
79 | @objc func ssdpBrowser(browser: SSDPServiceBrowser!, didNotStartBrowsingForServices error: NSError!) {
80 | failed🔰()
81 | delegate?.ssdpDiscoveryAdapter(self, didFailWithError: error)
82 | }
83 |
84 | @objc func ssdpBrowser(browser: SSDPServiceBrowser!, didFindService ssdpDiscoveryUnadapted: SSDPService!) {
85 | dispatch_async(_serialSSDPDiscoveryQueue, { () -> Void in
86 | if self._ssdpDiscoveries[ssdpDiscoveryUnadapted.uniqueServiceName] == nil &&
87 | returnIfContainsElements(ssdpDiscoveryUnadapted.uniqueServiceName) != nil &&
88 | ssdpDiscoveryUnadapted.location != nil &&
89 | returnIfContainsElements(ssdpDiscoveryUnadapted.serviceType) != nil {
90 | if let usn = UniqueServiceName(rawValue: ssdpDiscoveryUnadapted.uniqueServiceName),
91 | descriptionURL = ssdpDiscoveryUnadapted.location,
92 | ssdpType = SSDPType(rawValue: ssdpDiscoveryUnadapted.serviceType) {
93 | let ssdpDiscovery = SSDPDiscovery(usn: usn, descriptionURL: descriptionURL, type: ssdpType)
94 |
95 | self._ssdpDiscoveries[ssdpDiscoveryUnadapted.uniqueServiceName] = ssdpDiscovery
96 |
97 | self.notifyDelegate(ofDiscoveries: self._ssdpDiscoveries.values.array)
98 | }
99 | }
100 | })
101 | }
102 |
103 | @objc func ssdpBrowser(browser: SSDPServiceBrowser!, didRemoveService ssdpDiscoveryUnadapted: SSDPService!) {
104 | dispatch_async(_serialSSDPDiscoveryQueue, { () -> Void in
105 | if self._ssdpDiscoveries[ssdpDiscoveryUnadapted.uniqueServiceName] != nil {
106 | self._ssdpDiscoveries.removeValueForKey(ssdpDiscoveryUnadapted.uniqueServiceName)
107 |
108 | self.notifyDelegate(ofDiscoveries: self._ssdpDiscoveries.values.array)
109 | }
110 | })
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/FolderViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // FolderViewController.m
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | #import "FolderViewController.h"
25 | #import "Player.h"
26 | @import UPnAtom;
27 |
28 | @interface FolderViewController ()
29 | @property (nonatomic) IBOutlet UITableView *tableView;
30 | @end
31 |
32 | @implementation FolderViewController {
33 | NSString *_rootId;
34 | MediaServer1Device *_device;
35 | NSArray *_playlist;
36 | }
37 |
38 | - (void)viewDidLoad {
39 | [super viewDidLoad];
40 |
41 | [_device.contentDirectoryService getSortCapabilities:^(NSString *sortCaps) {
42 | if ([sortCaps rangeOfString:@"dc:title"].location != NSNotFound) {
43 | sortCaps = @"+dc:title";
44 | }
45 |
46 | [_device.contentDirectoryService browseWithObjectID:_rootId browseFlag:@"BrowseDirectChildren" filter:@"*" startingIndex:@"0" requestedCount:@"0" sortCriteria:sortCaps success:^(NSArray *result, NSInteger numberReturned, NSInteger totalMatches, NSString *updateID) {
47 | _playlist = result;
48 | [self.tableView reloadData];
49 | } failure:^(NSError *error) {
50 | NSLog(@"failed to browse content directory");
51 | }];
52 | } failure:^(NSError *error) {
53 | NSLog(@"failed to get sort capabilities");
54 | }];
55 |
56 | CGFloat viewWidth = self.navigationController.view.frame.size.width;
57 | UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0 , 11.0f, viewWidth - (viewWidth * 0.2), 21.0f)];
58 | [titleLabel setFont:[UIFont fontWithName:@"Helvetica" size:18]];
59 | [titleLabel setBackgroundColor:[UIColor clearColor]];
60 | [titleLabel setTextColor:[UIColor blackColor]];
61 |
62 | if([[Player sharedInstance] mediaRenderer] == nil){
63 | [titleLabel setText:@"No Renderer Selected"];
64 | }else{
65 | [titleLabel setText:[[[Player sharedInstance] mediaRenderer] friendlyName] ];
66 | }
67 |
68 | [titleLabel setTextAlignment:NSTextAlignmentLeft];
69 | UIBarButtonItem *title = [[UIBarButtonItem alloc] initWithCustomView:titleLabel];
70 | NSArray *items = @[[[Player sharedInstance] playPauseButton], [[Player sharedInstance] stopButton], title];
71 | self.toolbarItems = items;
72 |
73 | self.navigationController.toolbarHidden = NO;
74 | }
75 |
76 | - (void)configureWithDevice:(MediaServer1Device *)device header:(NSString *)header rootId:(NSString *)rootId{
77 | _device = device;
78 | _rootId = rootId;
79 | self.title = header;
80 | _playlist = [NSArray array];
81 | }
82 |
83 | #pragma mark - UITableViewDataSource methods
84 |
85 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
86 | return [_playlist count];
87 | }
88 |
89 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
90 | static NSString *cellIdentifier = @"DefaultCell";
91 |
92 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
93 |
94 | // Configure the cell...
95 | ContentDirectory1Object *item = _playlist[indexPath.row];
96 | [[cell textLabel] setText:[item title]];
97 |
98 | cell.accessoryType = item.isContentDirectory1Container ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
99 |
100 | return cell;
101 | }
102 |
103 | #pragma mark - UITableViewDelegate methods
104 |
105 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
106 | ContentDirectory1Object *item = _playlist[indexPath.row];
107 | if([item isContentDirectory1Container]){
108 | ContentDirectory1Container *container = _playlist[indexPath.row];
109 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
110 | FolderViewController *targetViewController = [storyboard instantiateViewControllerWithIdentifier:@"FolderViewControllerScene"];
111 | [targetViewController configureWithDevice:_device header:[container title] rootId:[container objectID]];
112 |
113 | [[self navigationController] pushViewController:targetViewController animated:YES];
114 | }else{
115 | [[Player sharedInstance] startPlayback:_playlist position:indexPath.row];
116 | }
117 | }
118 |
119 | @end
120 |
--------------------------------------------------------------------------------
/Source/HTTP Client Session Managers and Serializers/SOAPSerialization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SOAPSerialization.swift
3 | // ControlPointDemo
4 | //
5 | // SOAPSerialization.swift
6 | //
7 | // Copyright (c) 2015 David Robles
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in all
17 | // copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | // SOFTWARE.
26 |
27 | import Foundation
28 | import AFNetworking
29 | import Ono
30 |
31 | public class SOAPRequestSerializer: AFHTTPRequestSerializer {
32 | public class Parameters {
33 | let soapAction: String
34 | let serviceURN: String
35 | let arguments: NSDictionary?
36 |
37 | public init(soapAction: String, serviceURN: String, arguments: NSDictionary?) {
38 | self.soapAction = soapAction
39 | self.serviceURN = serviceURN
40 | self.arguments = arguments
41 | }
42 | }
43 |
44 | override public func requestBySerializingRequest(request: NSURLRequest, withParameters parameters: AnyObject?, error: NSErrorPointer) -> NSURLRequest? {
45 | guard let requestParameters = parameters as? Parameters else {
46 | return nil
47 | }
48 |
49 | let mutableRequest: NSMutableURLRequest = request.mutableCopy() as! NSMutableURLRequest
50 |
51 | for (field, value) in self.HTTPRequestHeaders {
52 | if let field = field as? String, value = value as? String where request.valueForHTTPHeaderField(field) == nil {
53 | mutableRequest.setValue(value, forHTTPHeaderField: field)
54 | }
55 | }
56 |
57 | if mutableRequest.valueForHTTPHeaderField("Content-Type") == nil {
58 | let charSet = CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))
59 | mutableRequest.setValue("text/xml; charset=\"\(charSet)\"", forHTTPHeaderField: "Content-Type")
60 | }
61 |
62 | mutableRequest.setValue("\"\(requestParameters.serviceURN)#\(requestParameters.soapAction)\"", forHTTPHeaderField: "SOAPACTION")
63 |
64 | var body = ""
65 | body += ""
66 | body += ""
67 | body += ""
68 | if let arguments = requestParameters.arguments {
69 | for (key, value) in arguments {
70 | body += "<\(key)>\(value)\(key)>"
71 | }
72 | }
73 | body += ""
74 | body += ""
75 | LogVerbose("SOAP request body: \(body)")
76 |
77 | mutableRequest.setValue("\(body.utf8.count)", forHTTPHeaderField: "Content-Length")
78 |
79 | mutableRequest.HTTPBody = body.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
80 |
81 | return mutableRequest
82 | }
83 | }
84 |
85 | public class SOAPResponseSerializer: AFXMLParserResponseSerializer {
86 | override public func responseObjectForResponse(response: NSURLResponse?, data: NSData?, error: NSErrorPointer) -> AnyObject? {
87 | do {
88 | try validateResponse(response as! NSHTTPURLResponse, data: data)
89 | } catch {
90 | return nil
91 | }
92 | let xmlParser = SOAPResponseParser()
93 |
94 | guard let data = data else {
95 | return nil
96 | }
97 |
98 | switch xmlParser.parse(soapResponseData: data) {
99 | case .Success(let value):
100 | return value
101 | case .Failure(let error):
102 | return nil
103 | }
104 | }
105 | }
106 |
107 | class SOAPResponseParser: AbstractDOMXMLParser {
108 | private var _responseParameters = [String: String]()
109 |
110 | override func parse(document document: ONOXMLDocument) -> EmptyResult {
111 | var result: EmptyResult = .Success
112 | document.enumerateElementsWithXPath("/s:Envelope/s:Body/*/*", usingBlock: { (element: ONOXMLElement!, index: UInt, stop: UnsafeMutablePointer) -> Void in
113 | if let elementTag = element.tag, elementValue = element.stringValue() where
114 | elementTag.characters.count > 0 && elementValue.characters.count > 0 && elementValue != "NOT_IMPLEMENTED" {
115 | self._responseParameters[elementTag] = elementValue
116 | }
117 |
118 | result = .Success
119 | })
120 |
121 | LogVerbose("SOAP response values: \(prettyPrint(_responseParameters))")
122 |
123 | return result
124 | }
125 |
126 | func parse(soapResponseData soapResponseData: NSData) -> Result<[String: String]> {
127 | switch super.parse(data: soapResponseData) {
128 | case .Success:
129 | return .Success(_responseParameters)
130 | case .Failure(let error):
131 | return .Failure(error)
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/Source/Logging/PropertyPrinter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PropertyPrinter.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | public protocol ExtendedPrintable: CustomStringConvertible {
27 | var className: String { get }
28 | }
29 |
30 | public struct PropertyPrinter {
31 | private var _properties = [String: String]()
32 |
33 | public init() { }
34 |
35 | public mutating func add(propertyName: String, property: T?) {
36 | _properties[propertyName] = prettyPrint(property)
37 | }
38 |
39 | public mutating func add(propertyName: String, property: T?) {
40 | _properties[propertyName] = prettyPrint(property)
41 | }
42 |
43 | public mutating func add(propertyName: String, property: [T]?) {
44 | _properties[propertyName] = prettyPrint(property)
45 | }
46 |
47 | public mutating func add(propertyName: String, property: [T]?) {
48 | _properties[propertyName] = prettyPrint(property)
49 | }
50 |
51 | public mutating func add(propertyName: String, property: [K: V]?) {
52 | _properties[propertyName] = prettyPrint(property)
53 | }
54 |
55 | public mutating func add(propertyName: String, property: [K: V]?) {
56 | _properties[propertyName] = prettyPrint(property)
57 | }
58 |
59 | // subscript(key: String) -> Any? { // subscripts can't be used as the subscript function can't be Generic only class/struct-wide generic declarations can be made which isn't useful here
60 | // get {
61 | // return nil
62 | // }
63 | // set {
64 | // add(key, property: newValue)
65 | // }
66 | // }
67 | }
68 |
69 | extension PropertyPrinter: CustomStringConvertible {
70 | public var description: String {
71 | return dictionaryDescription(_properties, pointsToSymbol: "=")
72 | }
73 | }
74 |
75 | /// helpPrint(), dictionaryDescription(), and arrayDescription() have so many variants due to the limitation in the Swift for detecting swift-protocol conformance at runtime. This restriction will be removed in a future release of swift: http://stackoverflow.com/questions/26909974/protocols-why-is-objc-required-for-conformance-checking-and-optional-requireme
76 | func prettyPrint(someDictionary: [K: V]?) -> String {
77 | if let someDictionary = someDictionary {
78 | return prettyPrint(dictionaryDescription(someDictionary, pointsToSymbol: ":"))
79 | }
80 |
81 | return "nil"
82 | }
83 |
84 | func prettyPrint(someDictionary: [K: V]?) -> String {
85 | if let someDictionary = someDictionary {
86 | return prettyPrint(dictionaryDescription(someDictionary, pointsToSymbol: ":"))
87 | }
88 |
89 | return "nil"
90 | }
91 |
92 | func prettyPrint(someArray: [T]?) -> String {
93 | if let someArray = someArray {
94 | return prettyPrint(arrayDescription(someArray))
95 | }
96 |
97 | return "nil"
98 | }
99 |
100 | func prettyPrint(someArray: [T]?) -> String {
101 | if let someArray = someArray {
102 | return prettyPrint(arrayDescription(someArray))
103 | }
104 |
105 | return "nil"
106 | }
107 |
108 | func prettyPrint(something: T?) -> String {
109 | if let something = something {
110 | return something.description.stringByReplacingOccurrencesOfString("\n", withString: "\n\t", options: .LiteralSearch)
111 | }
112 |
113 | return "nil"
114 | }
115 |
116 | func prettyPrint(something: T?) -> String {
117 | if let something = something {
118 | return "\(something)".stringByReplacingOccurrencesOfString("\n", withString: "\n\t", options: .LiteralSearch)
119 | }
120 |
121 | return "nil"
122 | }
123 |
124 | func dictionaryDescription(properties: [K: V], pointsToSymbol: String) -> String {
125 | var description = "{ \n"
126 | for (key, value) in properties {
127 | let valueDescription = value.description.stringByReplacingOccurrencesOfString("\n", withString: "\n\t", options: .LiteralSearch)
128 | description += "\t\(key) \(pointsToSymbol) \(valueDescription) \n"
129 | }
130 | description += "}"
131 | return description
132 | }
133 |
134 | func dictionaryDescription(properties: [K: V], pointsToSymbol: String) -> String {
135 | var description = "{ \n"
136 | for (key, value) in properties {
137 | description += "\t\(key) \(pointsToSymbol) \(value) \n"
138 | }
139 | description += "}"
140 | return description
141 | }
142 |
143 | func arrayDescription(array: [T]) -> String {
144 | var description = "{ \n"
145 | for item in array {
146 | let itemDescription = item.description.stringByReplacingOccurrencesOfString("\n", withString: "\n\t", options: .LiteralSearch)
147 | description += "\t\(itemDescription)\n"
148 | }
149 | description += "}"
150 | return description
151 | }
152 |
153 | func arrayDescription(array: [T]) -> String {
154 | var description = "{ \n"
155 | for item in array {
156 | description += "\t\(item)\n"
157 | }
158 | description += "}"
159 | return description
160 | }
161 |
--------------------------------------------------------------------------------
/Source/AV Profile/Services/ConnectionManager1Service.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConnectionManager1Service.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | public class ConnectionManager1Service: AbstractUPnPService {
27 | public func getProtocolInfo(success: (source: [String], sink: [String]) -> Void, failure: (error: NSError) -> Void) {
28 | let parameters = SOAPRequestSerializer.Parameters(soapAction: "GetProtocolInfo", serviceURN: urn, arguments: nil)
29 |
30 | soapSessionManager.POST(self.controlURL.absoluteString, parameters: parameters, success: { (task: NSURLSessionDataTask, responseObject: AnyObject?) -> Void in
31 | let responseObject = responseObject as? [String: String]
32 | success(source: responseObject?["Source"]?.componentsSeparatedByString(",") ?? [String](), sink: responseObject?["Sink"]?.componentsSeparatedByString(",") ?? [String]())
33 | }, failure: { (task: NSURLSessionDataTask?, error: NSError) -> Void in
34 | failure(error: error)
35 | })
36 | }
37 |
38 | public func prepareForConnection(remoteProtocolInfo remoteProtocolInfo: String, peerConnectionManager: String, peerConnectionID: String, direction: String, success: (connectionID: String?, avTransportID: String?, renderingControlServiceID: String?) -> Void, failure:(error: NSError) -> Void) {
39 | let arguments = [
40 | "RemoteProtocolInfo" : remoteProtocolInfo,
41 | "PeerConnectionManager" : peerConnectionManager,
42 | "PeerConnectionID" : peerConnectionID,
43 | "Direction" : direction]
44 |
45 | let parameters = SOAPRequestSerializer.Parameters(soapAction: "PrepareForConnection", serviceURN: urn, arguments: arguments)
46 |
47 | // Check if the optional SOAP action "PrepareForConnection" is supported
48 | supportsSOAPAction(actionParameters: parameters) { (isSupported) -> Void in
49 | if isSupported {
50 | self.soapSessionManager.POST(self.controlURL.absoluteString, parameters: parameters, success: { (task: NSURLSessionDataTask, responseObject: AnyObject?) -> Void in
51 | let responseObject = responseObject as? [String: String]
52 | success(connectionID: responseObject?["ConnectionID"], avTransportID: responseObject?["AVTransportID"], renderingControlServiceID: responseObject?["RcsID"])
53 | }, failure: { (task: NSURLSessionDataTask?, error: NSError) -> Void in
54 | failure(error: error)
55 | })
56 | } else {
57 | failure(error: createError("SOAP action '\(parameters.soapAction)' unsupported by service \(self.urn) on device \(self.device?.friendlyName)"))
58 | }
59 | }
60 | }
61 |
62 | public func connectionComplete(connectionID connectionID: String, success: () -> Void, failure:(error: NSError) -> Void) {
63 | let arguments = ["ConnectionID" : connectionID]
64 |
65 | let parameters = SOAPRequestSerializer.Parameters(soapAction: "ConnectionComplete", serviceURN: urn, arguments: arguments)
66 |
67 | // Check if the optional SOAP action "ConnectionComplete" is supported
68 | supportsSOAPAction(actionParameters: parameters) { (isSupported) -> Void in
69 | if isSupported {
70 | self.soapSessionManager.POST(self.controlURL.absoluteString, parameters: parameters, success: { (task: NSURLSessionDataTask, responseObject: AnyObject?) -> Void in
71 | success()
72 | }, failure: { (task: NSURLSessionDataTask?, error: NSError) -> Void in
73 | failure(error: error)
74 | })
75 | } else {
76 | failure(error: createError("SOAP action '\(parameters.soapAction)' unsupported by service \(self.urn) on device \(self.device?.friendlyName)"))
77 | }
78 | }
79 | }
80 |
81 | public func getCurrentConnectionIDs(success: (connectionIDs: [String]) -> Void, failure: (error: NSError) -> Void) {
82 | let parameters = SOAPRequestSerializer.Parameters(soapAction: "GetCurrentConnectionIDs", serviceURN: urn, arguments: nil)
83 |
84 | soapSessionManager.POST(self.controlURL.absoluteString, parameters: parameters, success: { (task: NSURLSessionDataTask, responseObject: AnyObject?) -> Void in
85 | let responseObject = responseObject as? [String: String]
86 | success(connectionIDs: responseObject?["ConnectionIDs"]?.componentsSeparatedByString(",") ?? [String]())
87 | }, failure: { (task: NSURLSessionDataTask?, error: NSError) -> Void in
88 | failure(error: error)
89 | })
90 | }
91 |
92 | public func getCurrentConnectionInfo(connectionID connectionID: String, success: (renderingControlServiceID: String?, avTransportID: String?, protocolInfo: String?, peerConnectionManager: String?, peerConnectionID: String?, direction: String?, status: String?) -> Void, failure: (error: NSError) -> Void) {
93 | let arguments = ["ConnectionID" : connectionID]
94 |
95 | let parameters = SOAPRequestSerializer.Parameters(soapAction: "GetCurrentConnectionInfo", serviceURN: urn, arguments: arguments)
96 |
97 | soapSessionManager.POST(self.controlURL.absoluteString, parameters: parameters, success: { (task: NSURLSessionDataTask, responseObject: AnyObject?) -> Void in
98 | let responseObject = responseObject as? [String: String]
99 | success(renderingControlServiceID: responseObject?["RcsID"], avTransportID: responseObject?["AVTransportID"], protocolInfo: responseObject?["ProtocolInfo"], peerConnectionManager: responseObject?["PeerConnectionManager"], peerConnectionID: responseObject?["PeerConnectionID"], direction: responseObject?["Direction"], status: responseObject?["Status"])
100 | }, failure: { (task: NSURLSessionDataTask?, error: NSError) -> Void in
101 | failure(error: error)
102 | })
103 | }
104 | }
105 |
106 | /// for objective-c type checking
107 | extension AbstractUPnP {
108 | public func isConnectionManager1Service() -> Bool {
109 | return self is ConnectionManager1Service
110 | }
111 | }
112 |
113 | /// overrides ExtendedPrintable protocol implementation
114 | extension ConnectionManager1Service {
115 | override public var className: String { return "\(self.dynamicType)" }
116 | override public var description: String {
117 | var properties = PropertyPrinter()
118 | properties.add(super.className, property: super.description)
119 | return properties.description
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_Swift/ControlPointDemo/Player.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Player.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import UIKit
25 | import UPnAtom
26 |
27 | private let _PlayerSharedInstance = Player()
28 |
29 | class Player {
30 | class var sharedInstance: Player {
31 | return _PlayerSharedInstance
32 | }
33 | var mediaServer: MediaServer1Device?
34 | var mediaRenderer: MediaRenderer1Device? {
35 | didSet {
36 | didSetRenderer(oldRenderer: oldValue, newRenderer: mediaRenderer)
37 | }
38 | }
39 | private(set) var playPauseButton: UIBarButtonItem! // TODO: Should ideally be a constant, see Github issue #10
40 | private(set) var stopButton: UIBarButtonItem! // TODO: Should ideally be a constant, see Github issue #10
41 |
42 | private var _position: Int = 0
43 | private var _playlist: [ContentDirectory1Object]?
44 | private var _avTransportEventObserver: AnyObject?
45 | private var _playerState: PlayerState = PlayerState.Stopped {
46 | didSet {
47 | playerStateDidChange()
48 | }
49 | }
50 | private var _avTransportInstanceID = "0"
51 |
52 | enum PlayerState {
53 | case Unknown
54 | case Stopped
55 | case Playing
56 | case Paused
57 | }
58 |
59 | init() {
60 | playPauseButton = UIBarButtonItem(image: UIImage(named: "play_button"), style: .Plain, target: self, action: #selector(Player.playPauseButtonTapped(_:)))
61 | stopButton = UIBarButtonItem(image: UIImage(named: "stop_button"), style: .Plain, target: self, action: #selector(Player.stopButtonTapped(_:)))
62 | }
63 |
64 | func startPlayback(playlist: [ContentDirectory1Object], position: Int) {
65 | _playlist = playlist
66 |
67 | startPlayback(position: position)
68 | }
69 |
70 | func startPlayback(position position: Int) {
71 | _position = position
72 |
73 | if let item = _playlist?[position] as? ContentDirectory1VideoItem {
74 | let uri = item.resourceURL.absoluteString
75 | let instanceID = _avTransportInstanceID
76 | mediaRenderer?.avTransportService?.setAVTransportURI(instanceID: instanceID, currentURI: uri, currentURIMetadata: "", success: { () -> Void in
77 | print("URI set succeeded!")
78 | self.play({ () -> Void in
79 | print("Play command succeeded!")
80 | }, failure: { (error) -> Void in
81 | print("Play command failed: \(error)")
82 | })
83 |
84 | }, failure: { (error) -> Void in
85 | print("URI set failed: \(error)")
86 | })
87 | }
88 | }
89 |
90 | @objc private func playPauseButtonTapped(sender: AnyObject) {
91 | print("play/pause button tapped")
92 |
93 | switch _playerState {
94 | case .Playing:
95 | pause({ () -> Void in
96 | print("Pause command succeeded!")
97 | }, failure: { (error) -> Void in
98 | print("Pause command failed: \(error)")
99 | })
100 | case .Paused, .Stopped:
101 | play({ () -> Void in
102 | print("Play command succeeded!")
103 | }, failure: { (error) -> Void in
104 | print("Play command failed: \(error)")
105 | })
106 | default:
107 | print("Play/pause button cannot be used in this state.")
108 | }
109 | }
110 |
111 | @objc private func stopButtonTapped(sender: AnyObject) {
112 | print("stop button tapped")
113 |
114 | switch _playerState {
115 | case .Playing, .Paused:
116 | stop({ () -> Void in
117 | print("Stop command succeeded!")
118 | }, failure: { (error) -> Void in
119 | print("Stop command failed: \(error)")
120 | })
121 | case .Stopped:
122 | print("Stop button cannot be used in this state.")
123 | default:
124 | print("Stop button cannot be used in this state.")
125 | }
126 | }
127 |
128 | private func didSetRenderer(oldRenderer oldRenderer: MediaRenderer1Device?, newRenderer: MediaRenderer1Device?) {
129 | if let avTransportEventObserver: AnyObject = _avTransportEventObserver {
130 | oldRenderer?.avTransportService?.removeEventObserver(avTransportEventObserver)
131 | }
132 |
133 | _avTransportEventObserver = newRenderer?.avTransportService?.addEventObserver(NSOperationQueue.currentQueue(), callBackBlock: { (event: UPnPEvent) -> Void in
134 | if let avTransportEvent = event as? AVTransport1Event,
135 | transportState = (avTransportEvent.instanceState["TransportState"] as? String)?.lowercaseString {
136 | print("\(event.service?.className) Event: \(avTransportEvent.instanceState)")
137 | print("transport state: \(transportState)")
138 | if transportState.rangeOfString("playing") != nil {
139 | self._playerState = .Playing
140 | }
141 | else if transportState.rangeOfString("paused") != nil {
142 | self._playerState = .Paused
143 | }
144 | else if transportState.rangeOfString("stopped") != nil {
145 | self._playerState = .Stopped
146 | }
147 | else {
148 | self._playerState = .Unknown
149 | }
150 | }
151 | })
152 | }
153 |
154 | private func playerStateDidChange() {
155 | switch _playerState {
156 | case .Stopped, .Paused, .Unknown:
157 | playPauseButton.image = UIImage(named: "play_button")
158 | case .Playing:
159 | playPauseButton.image = UIImage(named: "pause_button")
160 | }
161 | }
162 |
163 | private func play(success: () -> Void, failure:(error: NSError) -> Void) {
164 | self.mediaRenderer?.avTransportService?.play(instanceID: _avTransportInstanceID, speed: "1", success: success, failure: failure)
165 | }
166 |
167 | private func pause(success: () -> Void, failure:(error: NSError) -> Void) {
168 | self.mediaRenderer?.avTransportService?.pause(instanceID: _avTransportInstanceID, success: success, failure: failure)
169 | }
170 |
171 | private func stop(success: () -> Void, failure:(error: NSError) -> Void) {
172 | self.mediaRenderer?.avTransportService?.stop(instanceID: _avTransportInstanceID, success: success, failure: failure)
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/Source/HTTP Client Session Managers and Serializers/UPnPEventSubscriptionSerialization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UPnPEventSubscriptionSerialization.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 | import AFNetworking
26 |
27 | class UPnPEventSubscribeRequestSerializer: AFHTTPRequestSerializer {
28 | class Parameters {
29 | let callBack: NSURL
30 | let timeout: Int // in seconds
31 |
32 | init(callBack: NSURL, timeout: Int) {
33 | self.callBack = callBack
34 | self.timeout = timeout
35 | }
36 | }
37 |
38 | override func requestBySerializingRequest(request: NSURLRequest, withParameters parameters: AnyObject?, error: NSErrorPointer) -> NSURLRequest? {
39 | guard let requestParameters = parameters as? Parameters else {
40 | return nil
41 | }
42 |
43 | let mutableRequest: NSMutableURLRequest = request.mutableCopy() as! NSMutableURLRequest
44 |
45 | for (field, value) in self.HTTPRequestHeaders {
46 | if let field = field as? String, value = value as? String where request.valueForHTTPHeaderField(field) == nil {
47 | mutableRequest.setValue(value, forHTTPHeaderField: field)
48 | }
49 | }
50 |
51 | let callBackString = requestParameters.callBack.absoluteString
52 | mutableRequest.setValue("<\(callBackString)>", forHTTPHeaderField: "CALLBACK")
53 |
54 | mutableRequest.setValue("upnp:event", forHTTPHeaderField: "NT")
55 | mutableRequest.setValue("Second-\(requestParameters.timeout)", forHTTPHeaderField: "TIMEOUT")
56 |
57 | return mutableRequest
58 | }
59 | }
60 |
61 | class UPnPEventSubscribeResponseSerializer: AFHTTPResponseSerializer {
62 | class Response {
63 | let subscriptionID: String
64 | let timeout: Int // in seconds
65 |
66 | init(subscriptionID: String, timeout: Int) {
67 | self.subscriptionID = subscriptionID
68 | self.timeout = timeout
69 | }
70 | }
71 |
72 | override func responseObjectForResponse(response: NSURLResponse?, data: NSData?, error: NSErrorPointer) -> AnyObject? {
73 | do {
74 | try validateResponse(response as! NSHTTPURLResponse, data: data)
75 | } catch {
76 | return nil
77 | }
78 |
79 | if let subscriptionID = (response as! NSHTTPURLResponse).allHeaderFields["SID"] as? String,
80 | timeoutString = (response as! NSHTTPURLResponse).allHeaderFields["TIMEOUT"] as? String,
81 | secondKeywordRange = timeoutString.rangeOfString("Second-"),
82 | timeout = Int(timeoutString.substringWithRange(Range(start: secondKeywordRange.endIndex, end: timeoutString.endIndex))) {
83 | return Response(subscriptionID: subscriptionID, timeout: timeout)
84 | } else {
85 | return nil
86 | }
87 | }
88 | }
89 |
90 | class UPnPEventRenewSubscriptionRequestSerializer: AFHTTPRequestSerializer {
91 | class Parameters {
92 | let subscriptionID: String
93 | let timeout: Int // in seconds
94 |
95 | init(subscriptionID: String, timeout: Int) {
96 | self.subscriptionID = subscriptionID
97 | self.timeout = timeout
98 | }
99 | }
100 |
101 | override func requestBySerializingRequest(request: NSURLRequest, withParameters parameters: AnyObject?, error: NSErrorPointer) -> NSURLRequest? {
102 | guard let requestParameters = parameters as? Parameters else {
103 | return nil
104 | }
105 |
106 | let mutableRequest: NSMutableURLRequest = request.mutableCopy() as! NSMutableURLRequest
107 |
108 | for (field, value) in self.HTTPRequestHeaders {
109 | if let field = field as? String, value = value as? String where request.valueForHTTPHeaderField(field) == nil {
110 | mutableRequest.setValue(value, forHTTPHeaderField: field)
111 | }
112 | }
113 |
114 | mutableRequest.setValue("\(requestParameters.subscriptionID)", forHTTPHeaderField: "SID")
115 | mutableRequest.setValue("Second-\(requestParameters.timeout)", forHTTPHeaderField: "TIMEOUT")
116 |
117 | return mutableRequest
118 | }
119 | }
120 |
121 | class UPnPEventRenewSubscriptionResponseSerializer: AFHTTPResponseSerializer {
122 | class Response {
123 | let subscriptionID: String
124 | let timeout: Int // in seconds
125 |
126 | init(subscriptionID: String, timeout: Int) {
127 | self.subscriptionID = subscriptionID
128 | self.timeout = timeout
129 | }
130 | }
131 |
132 | override func responseObjectForResponse(response: NSURLResponse?, data: NSData?, error: NSErrorPointer) -> AnyObject? {
133 | do {
134 | try validateResponse(response as! NSHTTPURLResponse, data: data)
135 | } catch {
136 | return nil
137 | }
138 |
139 | guard let subscriptionID = (response as! NSHTTPURLResponse).allHeaderFields["SID"] as? String,
140 | timeoutString = (response as! NSHTTPURLResponse).allHeaderFields["TIMEOUT"] as? String,
141 | secondKeywordRange = timeoutString.rangeOfString("Second-"),
142 | timeout = Int(timeoutString.substringWithRange(Range(start: secondKeywordRange.endIndex, end: timeoutString.endIndex))) else {
143 | return nil
144 | }
145 |
146 | return Response(subscriptionID: subscriptionID, timeout: timeout)
147 | }
148 | }
149 |
150 | class UPnPEventUnsubscribeRequestSerializer: AFHTTPRequestSerializer {
151 | class Parameters {
152 | let subscriptionID: String
153 |
154 | init(subscriptionID: String) {
155 | self.subscriptionID = subscriptionID
156 | }
157 | }
158 |
159 | override func requestBySerializingRequest(request: NSURLRequest, withParameters parameters: AnyObject?, error: NSErrorPointer) -> NSURLRequest? {
160 | guard let requestParameters = parameters as? Parameters else {
161 | return nil
162 | }
163 |
164 | let mutableRequest: NSMutableURLRequest = request.mutableCopy() as! NSMutableURLRequest
165 |
166 | for (field, value) in self.HTTPRequestHeaders {
167 | if let field = field as? String, value = value as? String where request.valueForHTTPHeaderField(field) == nil {
168 | mutableRequest.setValue(value, forHTTPHeaderField: field)
169 | }
170 | }
171 |
172 | mutableRequest.setValue("\(requestParameters.subscriptionID)", forHTTPHeaderField: "SID")
173 |
174 | return mutableRequest
175 | }
176 | }
177 |
178 | class UPnPEventUnsubscribeResponseSerializer: AFHTTPResponseSerializer {
179 | override func responseObjectForResponse(response: NSURLResponse?, data: NSData?, error: NSErrorPointer) -> AnyObject? {
180 | do {
181 | try validateResponse(response as! NSHTTPURLResponse, data: data)
182 | } catch {
183 | return nil
184 | }
185 |
186 | return "Success"
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/Source/Parsers/AbstractSAXXMLParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AbstractSAXXMLParser.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | // Subclassing NSObject in order to be a NSXMLParserDelegate
27 | public class AbstractSAXXMLParser: NSObject {
28 | private let _supportNamespaces: Bool
29 | lazy private var _elementStack = [String]()
30 | lazy private var _elementObservations = [SAXXMLParserElementObservation]()
31 |
32 | public init(supportNamespaces: Bool) {
33 | _supportNamespaces = supportNamespaces
34 | }
35 |
36 | convenience override init() {
37 | self.init(supportNamespaces: false)
38 | }
39 |
40 | public func addElementObservation(elementObservation: SAXXMLParserElementObservation) {
41 | _elementObservations.append(elementObservation)
42 | }
43 |
44 | public func clearAllElementObservations() {
45 | _elementObservations.removeAll(keepCapacity: false)
46 | }
47 |
48 | func elementObservationForElementStack(elementStack: [String]) -> SAXXMLParserElementObservation? {
49 | for elementObservation in _elementObservations {
50 | // Full compares go first
51 | guard elementObservation.elementPath != elementStack else {
52 | return elementObservation
53 | }
54 |
55 | // * -> leafX -> leafY
56 | // Maybe we have a wildchar, that means that the path after the wildchar must match
57 | if elementObservation.elementPath.first == "*" &&
58 | elementStack.count >= elementObservation.elementPath.count {
59 | var tempElementStack = elementStack
60 | var tempObservationElementPath = elementObservation.elementPath
61 |
62 | // cut the * from our asset path
63 | tempObservationElementPath.removeAtIndex(0)
64 |
65 | // make our (copy of the) curents stack the same length
66 | let elementsToRemove: Int = tempElementStack.count - tempObservationElementPath.count
67 | let range = Range(start: 0, end: elementsToRemove)
68 | tempElementStack.removeRange(range)
69 | if tempObservationElementPath == tempElementStack {
70 | return elementObservation
71 | }
72 | }
73 |
74 | // leafX -> leafY -> *
75 | if elementObservation.elementPath.last == "*" &&
76 | elementStack.count == elementObservation.elementPath.count && elementStack.count > 1 {
77 | var tempElementStack = elementStack
78 | var tempObservationElementPath = elementObservation.elementPath
79 | // Cut the last entry (which is * in one array and in the other
80 | tempElementStack.removeLast()
81 | tempObservationElementPath.removeLast()
82 | if tempElementStack == tempObservationElementPath {
83 | return elementObservation
84 | }
85 | }
86 | }
87 |
88 | return nil
89 | }
90 |
91 | public func parse(data data: NSData) -> EmptyResult {
92 | var parserResult: EmptyResult = .Failure(createError("Parser failure"))
93 | autoreleasepool { () -> () in
94 | if let validData = self.validateForParsing(data) {
95 | let parser = NSXMLParser(data: validData)
96 | parserResult = self.startParser(parser)
97 | }
98 | }
99 |
100 | return parserResult
101 | }
102 |
103 | // MARK: - Internal lib
104 |
105 | private func startParser(parser: NSXMLParser) -> EmptyResult {
106 | parser.shouldProcessNamespaces = _supportNamespaces
107 | parser.delegate = self
108 |
109 | var parserResult: EmptyResult = .Failure(createError("Parser failure"))
110 | if parser.parse() {
111 | parserResult = .Success
112 | } else {
113 | if let parserError = parser.parserError {
114 | parserResult = .Failure(parserError)
115 | }
116 | }
117 |
118 | parser.delegate = nil
119 |
120 | return parserResult
121 | }
122 |
123 | private func validateForParsing(data: NSData) -> NSData? {
124 | guard let xmlStringOptional = NSString(data: data, encoding: NSUTF8StringEncoding),
125 | let regexOptional = try? NSRegularExpression(pattern: "^\\s*$\\r?\\n", options: .AnchorsMatchLines) else {
126 | return nil
127 | }
128 |
129 | let validXMLString = regexOptional.stringByReplacingMatchesInString(xmlStringOptional as String, options: NSMatchingOptions(rawValue: 0), range: NSMakeRange(0, xmlStringOptional.length), withTemplate: "")
130 | return validXMLString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
131 | }
132 | }
133 |
134 | extension AbstractSAXXMLParser: NSXMLParserDelegate {
135 | public func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
136 | _elementStack += [elementName]
137 |
138 | if let elementObservation = elementObservationForElementStack(_elementStack),
139 | didStartParsingElement = elementObservation.didStartParsingElement {
140 | didStartParsingElement(elementName: elementName, attributeDict: attributeDict)
141 | }
142 | }
143 |
144 | public func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
145 | if let elementObservation = elementObservationForElementStack(_elementStack) {
146 | let foundInnerText = elementObservation.foundInnerText
147 | let innerText = elementObservation.innerText
148 | if foundInnerText != nil && innerText != nil {
149 | foundInnerText!(elementName: elementName, text: elementObservation.innerText!)
150 | }
151 |
152 | if let didEndParsingElement = elementObservation.didEndParsingElement {
153 | didEndParsingElement(elementName: elementName)
154 | }
155 | }
156 |
157 | if elementName == _elementStack.last {
158 | _elementStack.removeLast()
159 | } else {
160 | LogError("XML badly formatted!")
161 | parser.abortParsing()
162 | }
163 | }
164 |
165 | public func parser(parser: NSXMLParser, foundCharacters string: String) {
166 | // The parser object may send the delegate several parser:foundCharacters: messages to report the characters of an element. Because string may be only part of the total character content for the current element, you should append it to the current accumulation of characters until the element changes.
167 |
168 | if let elementObservation = elementObservationForElementStack(_elementStack) {
169 | elementObservation.appendInnerText(string)
170 | }
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/Source/UPnP Foundation/GlobalLib.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GlobalLib.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 |
26 | func curlRep(request: NSURLRequest) -> String {
27 | var curl = ""
28 |
29 | curl += "curl -i"
30 |
31 | if let httpMethod = request.HTTPMethod {
32 | curl += " -X \(httpMethod)"
33 | }
34 |
35 | if let httpBody = request.HTTPBody,
36 | body = NSString(data: httpBody, encoding: NSUTF8StringEncoding) {
37 | curl += " -d '"
38 | curl += "\(body)"
39 | curl += "'"
40 | }
41 |
42 | if let allHTTPHeaderFields = request.allHTTPHeaderFields {
43 | for (key, value) in allHTTPHeaderFields {
44 | curl += " -H '\(key): \(value)'"
45 | }
46 | }
47 |
48 | curl += " \"\(request.URL)\""
49 |
50 | return curl
51 | }
52 |
53 | public typealias Error = NSError
54 |
55 | public enum Result {
56 | case Success(T)
57 | case Failure(Error)
58 |
59 | init(_ value: T) {
60 | self = .Success(value)
61 | }
62 |
63 | init(_ error: Error) {
64 | self = .Failure(error)
65 | }
66 |
67 | var failed: Bool {
68 | if case .Failure(_) = self {
69 | return true
70 | }
71 | return false
72 | }
73 |
74 | var error: Error? {
75 | if case .Failure(let error) = self {
76 | return error
77 | }
78 | return nil
79 | }
80 |
81 | var value: T? {
82 | if case .Success(let value) = self {
83 | return value
84 | }
85 | return nil
86 | }
87 | }
88 |
89 | public enum EmptyResult {
90 | case Success
91 | case Failure(Error)
92 |
93 | init() {
94 | self = .Success
95 | }
96 |
97 | init(_ error: Error) {
98 | self = .Failure(error)
99 | }
100 |
101 | var failed: Bool {
102 | if case .Failure(_) = self {
103 | return true
104 | }
105 | return false
106 | }
107 |
108 | var error: Error? {
109 | if case .Failure(let error) = self {
110 | return error
111 | }
112 | return nil
113 | }
114 | }
115 |
116 | func createError(message: String) -> Error {
117 | return NSError(domain: "UPnAtom", code: 0, userInfo: [NSLocalizedDescriptionKey: message])
118 | }
119 |
120 | extension RangeReplaceableCollectionType where Generator.Element : Equatable {
121 | mutating func removeObject(object: Generator.Element) -> Generator.Element? {
122 | if let found = self.indexOf(object) {
123 | return removeAtIndex(found)
124 | }
125 | return nil
126 | }
127 | }
128 |
129 | extension NSError {
130 | /// An alternative to iOS's [NSError localizedDescription] which returns an esoteric Cocoa error when [NSError userInfo][NSLocalizedDescriptionKey] is nil. In that case, this method will return nil instead.
131 | var localizedDescriptionOrNil: String? {
132 | return self.userInfo[NSLocalizedDescriptionKey] as? String
133 | }
134 |
135 | func localizedDescription(defaultDescription: String) -> String {
136 | return self.localizedDescriptionOrNil != nil ? self.localizedDescriptionOrNil! : defaultDescription
137 | }
138 | }
139 |
140 | /// Until Apple provides modules for all of the missing C libraries, there's no good way to perform this task in Swift.
141 | //func getIFAddresses() -> [String: String] {
142 | // var addresses = [String: String]()
143 | //
144 | // // Get list of all interfaces on the local machine:
145 | // var ifaddr : UnsafeMutablePointer = nil
146 | // if getifaddrs(&ifaddr) == 0 {
147 | //
148 | // // For each interface ...
149 | // for (var ptr = ifaddr; ptr != nil; ptr = ptr.memory.ifa_next) {
150 | // let flags = Int32(ptr.memory.ifa_flags)
151 | // var addr = ptr.memory.ifa_addr.memory
152 | // let interfaceName = String.fromCString(ptr.memory.ifa_name)
153 | //
154 | // // Check for running IPv4, IPv6 interfaces. Skip the loopback interface.
155 | // if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {
156 | // if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6) {
157 | //
158 | // // Convert interface address to a human readable string:
159 | // var hostname = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
160 | // if (getnameinfo(&addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),
161 | // nil, socklen_t(0), NI_NUMERICHOST) == 0) {
162 | // let address = String.fromCString(hostname)
163 | // if address != nil && interfaceName != nil {
164 | // addresses[interfaceName!] = address!
165 | // }
166 | // }
167 | // }
168 | // }
169 | // }
170 | // freeifaddrs(ifaddr)
171 | // }
172 | //
173 | // return addresses
174 | //}
175 |
176 | extension NSTimer {
177 | private class NSTimerClosureHandler {
178 | var closure: () -> Void
179 |
180 | init(closure: () -> Void) {
181 | self.closure = closure
182 | }
183 |
184 | dynamic func fire() {
185 | closure()
186 | }
187 | }
188 |
189 | convenience init(timeInterval: NSTimeInterval, repeats: Bool, closure: (() -> Void)) {
190 | let closureHandler = NSTimerClosureHandler(closure: closure)
191 | self.init(timeInterval: timeInterval, target: closureHandler, selector: "fire", userInfo: nil, repeats: repeats)
192 | }
193 |
194 | class func scheduledTimerWithTimeInterval(timeInterval: NSTimeInterval, repeats: Bool, closure: (() -> Void)) -> NSTimer {
195 | let closureHandler = NSTimerClosureHandler(closure: closure)
196 | return self.scheduledTimerWithTimeInterval(timeInterval, target: closureHandler, selector: "fire", userInfo: nil, repeats: repeats)
197 | }
198 | }
199 |
200 | extension NSArray {
201 | func firstUsingPredicate(predicate: NSPredicate) -> T? {
202 | return self.filteredArrayUsingPredicate(predicate).first as? T
203 | }
204 | }
205 |
206 | extension NSUUID {
207 | var dashlessUUIDString: String {
208 | return self.UUIDString.stringByReplacingOccurrencesOfString("-", withString: "", options: .LiteralSearch)
209 | }
210 | }
211 |
212 | struct Stack {
213 | var elements = [T]()
214 |
215 | mutating func push(element: T) {
216 | elements.append(element)
217 | }
218 |
219 | mutating func pop() -> T {
220 | return elements.removeLast()
221 | }
222 |
223 | func peek() -> T? {
224 | return elements.last
225 | }
226 |
227 | func isEmpty() -> Bool {
228 | return elements.isEmpty
229 | }
230 | }
231 |
232 | func + (left: Dictionary, right: Dictionary) -> Dictionary {
233 | var result = Dictionary()
234 |
235 | for (k, v) in left {
236 | result[k] = v
237 | }
238 |
239 | for (k, v) in right {
240 | result[k] = v
241 | }
242 |
243 | return result
244 | }
245 |
246 | func += (inout left: Dictionary, right: Dictionary) {
247 | for (k, v) in right {
248 | left[k] = v
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/Source/AV Profile/Services/ContentDirectory1Objects.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentDirectory1Object.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 | import Ono
26 |
27 | // MARK: ContentDirectory1Object
28 |
29 | /// TODO: For now rooting to NSObject to expose to Objective-C, see Github issue #16
30 | public class ContentDirectory1Object: NSObject {
31 | public let objectID: String
32 | public let parentID: String
33 | public let title: String
34 | public let rawType: String
35 | public let albumArtURL: NSURL?
36 |
37 | init?(xmlElement: ONOXMLElement) {
38 | if let objectID = xmlElement.valueForAttribute("id") as? String,
39 | parentID = xmlElement.valueForAttribute("parentID") as? String,
40 | title = xmlElement.firstChildWithTag("title").stringValue(),
41 | rawType = xmlElement.firstChildWithTag("class").stringValue() {
42 | self.objectID = objectID
43 | self.parentID = parentID
44 | self.title = title
45 | self.rawType = rawType
46 |
47 | if let albumArtURLString = xmlElement.firstChildWithTag("albumArtURI")?.stringValue() {
48 | self.albumArtURL = NSURL(string: albumArtURLString)
49 | } else { albumArtURL = nil }
50 | } else {
51 | /// TODO: Remove default initializations to simply return nil, see Github issue #11
52 | objectID = ""
53 | parentID = ""
54 | title = ""
55 | rawType = ""
56 | albumArtURL = nil
57 | super.init()
58 | return nil
59 | }
60 |
61 | super.init()
62 | }
63 | }
64 |
65 | extension ContentDirectory1Object: ExtendedPrintable {
66 | #if os(iOS)
67 | public var className: String { return "\(self.dynamicType)" }
68 | #elseif os(OSX) // NSObject.className actually exists on OSX! Who knew.
69 | override public var className: String { return "\(self.dynamicType)" }
70 | #endif
71 | override public var description: String {
72 | var properties = PropertyPrinter()
73 | properties.add("id", property: objectID)
74 | properties.add("parentID", property: parentID)
75 | properties.add("title", property: title)
76 | properties.add("class", property: rawType)
77 | properties.add("albumArtURI", property: albumArtURL?.absoluteString)
78 | return properties.description
79 | }
80 | }
81 |
82 | // MARK: - ContentDirectory1Container
83 |
84 | public class ContentDirectory1Container: ContentDirectory1Object {
85 | public let childCount: Int?
86 |
87 | override init?(xmlElement: ONOXMLElement) {
88 | self.childCount = Int(String(xmlElement.valueForAttribute("childCount")))
89 |
90 | super.init(xmlElement: xmlElement)
91 | }
92 | }
93 |
94 | /// for objective-c type checking
95 | extension ContentDirectory1Object {
96 | public func isContentDirectory1Container() -> Bool {
97 | return self is ContentDirectory1Container
98 | }
99 | }
100 |
101 | /// overrides ExtendedPrintable protocol implementation
102 | extension ContentDirectory1Container {
103 | override public var className: String { return "\(self.dynamicType)" }
104 | override public var description: String {
105 | var properties = PropertyPrinter()
106 | properties.add(super.className, property: super.description)
107 | properties.add("childCount", property: "\(childCount)")
108 | return properties.description
109 | }
110 | }
111 |
112 | // MARK: - ContentDirectory1Item
113 |
114 | public class ContentDirectory1Item: ContentDirectory1Object {
115 | public let resourceURL: NSURL!
116 |
117 | override init?(xmlElement: ONOXMLElement) {
118 | /// TODO: Return nil immediately instead of waiting, see Github issue #11
119 | if let resourceURLString = xmlElement.firstChildWithTag("res").stringValue() {
120 | resourceURL = NSURL(string: resourceURLString)
121 | } else { resourceURL = nil }
122 |
123 | super.init(xmlElement: xmlElement)
124 |
125 | guard resourceURL != nil else {
126 | return nil
127 | }
128 | }
129 | }
130 |
131 | /// for objective-c type checking
132 | extension ContentDirectory1Object {
133 | public func isContentDirectory1Item() -> Bool {
134 | return self is ContentDirectory1Item
135 | }
136 | }
137 |
138 | /// overrides ExtendedPrintable protocol implementation
139 | extension ContentDirectory1Item {
140 | override public var className: String { return "\(self.dynamicType)" }
141 | override public var description: String {
142 | var properties = PropertyPrinter()
143 | properties.add(super.className, property: super.description)
144 | properties.add("resourceURL", property: resourceURL?.absoluteString)
145 | return properties.description
146 | }
147 | }
148 |
149 | // MARK: - ContentDirectory1VideoItem
150 |
151 | public class ContentDirectory1VideoItem: ContentDirectory1Item {
152 | public let bitrate: Int?
153 | public let duration: NSTimeInterval?
154 | public let audioChannelCount: Int?
155 | public let protocolInfo: String?
156 | public let resolution: CGSize?
157 | public let sampleFrequency: Int?
158 | public let size: Int?
159 |
160 | override init?(xmlElement: ONOXMLElement) {
161 | bitrate = Int(String(xmlElement.firstChildWithTag("res").valueForAttribute("bitrate")))
162 |
163 | if let durationString = xmlElement.firstChildWithTag("res").valueForAttribute("duration") as? String {
164 | let durationComponents = durationString.componentsSeparatedByString(":")
165 | var count: Double = 0
166 | var duration: Double = 0
167 | for durationComponent in durationComponents.reverse() {
168 | duration += (durationComponent as NSString).doubleValue * pow(60, count)
169 | count++
170 | }
171 |
172 | self.duration = NSTimeInterval(duration)
173 | } else { self.duration = nil }
174 |
175 | audioChannelCount = Int(String(xmlElement.firstChildWithTag("res").valueForAttribute("nrAudioChannels")))
176 |
177 | protocolInfo = xmlElement.firstChildWithTag("res").valueForAttribute("protocolInfo") as? String
178 |
179 | if let resolutionComponents = (xmlElement.firstChildWithTag("res").valueForAttribute("resolution") as? String)?.componentsSeparatedByString("x"),
180 | width = Int(String(resolutionComponents.first)),
181 | height = Int(String(resolutionComponents.last)) {
182 | resolution = CGSize(width: width, height: height)
183 | } else { resolution = nil }
184 |
185 | sampleFrequency = Int(String(xmlElement.firstChildWithTag("res").valueForAttribute("sampleFrequency")))
186 |
187 | size = Int(String(xmlElement.firstChildWithTag("res").valueForAttribute("size")))
188 |
189 | super.init(xmlElement: xmlElement)
190 | }
191 | }
192 |
193 | /// for objective-c type checking
194 | extension ContentDirectory1Object {
195 | public func isContentDirectory1VideoItem() -> Bool {
196 | return self is ContentDirectory1VideoItem
197 | }
198 | }
199 |
200 | /// overrides ExtendedPrintable protocol implementation
201 | extension ContentDirectory1VideoItem {
202 | override public var className: String { return "\(self.dynamicType)" }
203 | override public var description: String {
204 | var properties = PropertyPrinter()
205 | properties.add(super.className, property: super.description)
206 | properties.add("bitrate", property: "\(bitrate)")
207 | properties.add("duration", property: "\(duration)")
208 | properties.add("audioChannelCount", property: "\(audioChannelCount)")
209 | properties.add("protocolInfo", property: protocolInfo)
210 | properties.add("resolution", property: "\(resolution?.width)x\(resolution?.height)")
211 | properties.add("sampleFrequency", property: "\(sampleFrequency)")
212 | properties.add("size", property: "\(size)")
213 | return properties.description
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/Examples/ControlPointDemo_ObjC/ControlPointDemo/Player.m:
--------------------------------------------------------------------------------
1 | //
2 | // Player.m
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | #import "Player.h"
25 | @import UPnAtom;
26 |
27 | typedef NS_ENUM(NSInteger, PlayerState) {
28 | PlayerStateUnknown = 0,
29 | PlayerStateStopped,
30 | PlayerStatePlaying,
31 | PlayerStatePaused
32 | };
33 |
34 | @interface Player ()
35 | @property (nonatomic, readwrite) NSArray *playlist;
36 | @property (nonatomic, readwrite) UIBarButtonItem *playPauseButton;
37 | @property (nonatomic, readwrite) UIBarButtonItem *stopButton;
38 | @property (nonatomic) PlayerState playerState;
39 | @end
40 |
41 | @implementation Player {
42 | NSInteger _position;
43 | id _avTransportEventObserver;
44 | NSString *_avTransportInstanceID;
45 | }
46 |
47 | + (Player *)sharedInstance {
48 | static Player *instance = nil;
49 | static dispatch_once_t onceToken;
50 |
51 | dispatch_once(&onceToken, ^{
52 | instance = [Player new];
53 |
54 | instance.playPauseButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"play_button"] style:UIBarButtonItemStylePlain target:instance action:@selector(playPauseButtonTapped:)];
55 | instance.stopButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"stop_button"] style:UIBarButtonItemStylePlain target:instance action:@selector(stopButtonTapped:)];
56 | instance->_avTransportInstanceID = @"0";
57 | });
58 |
59 | return instance;
60 | }
61 |
62 | - (void)setMediaRenderer:(MediaRenderer1Device *)mediaRenderer {
63 | MediaRenderer1Device *oldRenderer = _mediaRenderer;
64 | _mediaRenderer = mediaRenderer;
65 |
66 | if (_avTransportEventObserver != nil) {
67 | [oldRenderer.avTransportService removeEventObserver:_avTransportEventObserver];
68 | }
69 |
70 | _avTransportEventObserver = [mediaRenderer.avTransportService addEventObserver:[NSOperationQueue currentQueue] callBackBlock:^(UPnPEvent *event) {
71 | if ([event.service isAVTransport1Service] && [event isAVTransport1Event]) {
72 | AVTransport1Event *avTransportEvent = (AVTransport1Event *)event;
73 | NSLog(@"%@ Event: %@", event.service.className, avTransportEvent.instanceState);
74 | NSString *transportState = [avTransportEvent.instanceState[@"TransportState"] lowercaseString];
75 | if (transportState.length) {
76 | if ([transportState rangeOfString:@"playing"].location != NSNotFound) {
77 | self.playerState = PlayerStatePlaying;
78 | }
79 | else if ([transportState rangeOfString:@"paused"].location != NSNotFound) {
80 | self.playerState = PlayerStatePaused;
81 | }
82 | else if ([transportState rangeOfString:@"stopped"].location != NSNotFound) {
83 | self.playerState = PlayerStateStopped;
84 | }
85 | else {
86 | self.playerState = PlayerStateUnknown;
87 | }
88 | }
89 | }
90 | }];
91 | }
92 |
93 | - (void)startPlayback:(NSArray *)playList position:(NSInteger)position{
94 | [self setPlaylist:playList];
95 |
96 | [self startPlayback:position];
97 | }
98 |
99 | - (void)startPlayback:(NSInteger)position{
100 | //Do we have a Renderer & a playlist ?
101 | if(_mediaRenderer == nil || _playlist == nil){
102 | return;
103 | }
104 |
105 | if(position >= [_playlist count]){
106 | position = 0; //Loop
107 | }
108 |
109 | _position = position;
110 |
111 | ContentDirectory1Object *item = _playlist[position];
112 | //Is it a Media1ServerItem ?
113 | if([item isContentDirectory1VideoItem]){
114 | ContentDirectory1VideoItem *item = _playlist[position];
115 |
116 | NSString *uri = [item resourceURL].absoluteString;
117 | if (uri.length) {
118 | __weak typeof(self) weakSelf = self;
119 | [self.mediaRenderer.avTransportService setAVTransportURIWithInstanceID:_avTransportInstanceID currentURI:uri currentURIMetadata:@"" success:^{
120 | NSLog(@"URI Set succeeded!");
121 |
122 | [weakSelf playWithSuccess:^{
123 | NSLog(@"Play command succeeded!");
124 | } failure:^(NSError *error) {
125 | NSLog(@"Play command failed: %@", error.localizedDescription);
126 | }];
127 | } failure:^(NSError *error) {
128 | NSLog(@"URI Set failed: %@", error.localizedDescription);
129 | }];
130 | }
131 | }
132 | }
133 |
134 | #pragma mark - Internal lib
135 |
136 | - (void)playPauseButtonTapped:(id)sender {
137 | switch (self.playerState) {
138 | case PlayerStatePlaying:
139 | [self pauseWithSuccess:^{
140 | NSLog(@"Pause command succeeded!");
141 | } failure:^(NSError *error) {
142 | NSLog(@"Pause command failed: %@", error.localizedDescription);
143 | }];
144 | break;
145 |
146 | case PlayerStatePaused:
147 | [self playWithSuccess:^{
148 | NSLog(@"Play command succeeded!");
149 | } failure:^(NSError *error) {
150 | NSLog(@"Play command failed: %@", error.localizedDescription);
151 | }];
152 | break;
153 |
154 | case PlayerStateStopped:
155 | [self playWithSuccess:^{
156 | NSLog(@"Play command succeeded!");
157 | } failure:^(NSError *error) {
158 | NSLog(@"Play command failed: %@", error.localizedDescription);
159 | }];
160 | break;
161 |
162 | default:
163 | NSLog(@"Play/Pause button cannot be used in this state.");
164 | break;
165 | }
166 | }
167 |
168 | - (void)stopButtonTapped:(id)sender {
169 | switch (self.playerState) {
170 | case PlayerStatePlaying:
171 | [self stopWithSuccess:^{
172 | NSLog(@"Stop command succeeded!");
173 | } failure:^(NSError *error) {
174 | NSLog(@"Stop command failed: %@", error.localizedDescription);
175 | }];
176 | break;
177 |
178 | case PlayerStatePaused:
179 | [self stopWithSuccess:^{
180 | NSLog(@"Stop command succeeded!");
181 | } failure:^(NSError *error) {
182 | NSLog(@"Stop command failed: %@", error.localizedDescription);
183 | }];
184 | break;
185 |
186 | case PlayerStateStopped:
187 | NSLog(@"Stop button cannot be used in this state.");
188 | break;
189 |
190 | default:
191 | NSLog(@"Stop button cannot be used in this state.");
192 | break;
193 | }
194 | }
195 |
196 | - (void)setPlayerState:(PlayerState)playerState {
197 | _playerState = playerState;
198 |
199 | switch (playerState) {
200 | case PlayerStateStopped:
201 | self.playPauseButton.image = [UIImage imageNamed:@"play_button"];
202 | break;
203 |
204 | case PlayerStatePlaying:
205 | self.playPauseButton.image = [UIImage imageNamed:@"pause_button"];
206 | break;
207 |
208 | case PlayerStatePaused:
209 | self.playPauseButton.image = [UIImage imageNamed:@"play_button"];
210 | break;
211 |
212 | default:
213 | self.playPauseButton.image = [UIImage imageNamed:@"play_button"];
214 | break;
215 | }
216 | }
217 |
218 | - (void)playWithSuccess:(void (^)(void))success failure:(void (^)(NSError *))failure {
219 | [self.mediaRenderer.avTransportService playWithInstanceID:_avTransportInstanceID speed:@"1" success:success failure:failure];
220 | }
221 |
222 | - (void)pauseWithSuccess:(void (^)(void))success failure:(void (^)(NSError *))failure {
223 | [self.mediaRenderer.avTransportService pauseWithInstanceID:_avTransportInstanceID success:success failure:failure];
224 | }
225 |
226 | - (void)stopWithSuccess:(void (^)(void))success failure:(void (^)(NSError *))failure {
227 | [self.mediaRenderer.avTransportService stopWithInstanceID:_avTransportInstanceID success:success failure:failure];
228 | }
229 |
230 | @end
231 |
--------------------------------------------------------------------------------
/Source/SSDP/Explorer/SSDPExplorer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SSDPExplorer.swift
3 | //
4 | // Copyright (c) 2015 David Robles
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in all
14 | // copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | // SOFTWARE.
23 |
24 | import Foundation
25 | import CocoaAsyncSocket
26 | import AFNetworking
27 |
28 | protocol SSDPExplorerDelegate: class {
29 | func ssdpExplorer(explorer: SSDPExplorer, didMakeDiscovery discovery: SSDPDiscovery)
30 | // Removed discoveries will have an invalid desciption URL
31 | func ssdpExplorer(explorer: SSDPExplorer, didRemoveDiscovery discovery: SSDPDiscovery)
32 | /// Assume explorer has stopped after a failure.
33 | func ssdpExplorer(explorer: SSDPExplorer, didFailWithError error: NSError)
34 | }
35 |
36 | class SSDPExplorer {
37 | enum SSDPMessageType {
38 | case SearchResponse
39 | case AvailableNotification
40 | case UpdateNotification
41 | case UnavailableNotification
42 | }
43 |
44 | weak var delegate: SSDPExplorerDelegate?
45 |
46 | // private
47 | private static let _multicastGroupAddress = "239.255.255.250"
48 | private static let _multicastUDPPort: UInt16 = 1900
49 | private var _multicastSocket: GCDAsyncUdpSocket? // TODO: Should ideally be a constant, see Github issue #10
50 | private var _unicastSocket: GCDAsyncUdpSocket? // TODO: Should ideally be a constant, see Github issue #10
51 | private var _types = [SSDPType]() // TODO: Should ideally be a Set, see Github issue #13
52 |
53 | func startExploring(forTypes types: [SSDPType], onInterface interface: String = "en0") -> EmptyResult {
54 | assert(_multicastSocket == nil, "Socket is already open, stop it first!")
55 |
56 | // create sockets
57 | guard let multicastSocket: GCDAsyncUdpSocket! = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue()),
58 | unicastSocket: GCDAsyncUdpSocket! = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue()) else {
59 | return .Failure(createError("Socket could not be created"))
60 | }
61 | _multicastSocket = multicastSocket
62 | _unicastSocket = unicastSocket
63 | multicastSocket.setIPv6Enabled(false)
64 | unicastSocket.setIPv6Enabled(false)
65 |
66 | // Configure unicast socket
67 | // Bind to address on the specified interface to a random port to receive unicast datagrams
68 | do {
69 | try unicastSocket.bindToPort(0, interface: interface)
70 | } catch {
71 | stopExploring()
72 | return .Failure(createError("Could not bind socket to port"))
73 | }
74 |
75 | do {
76 | try unicastSocket.beginReceiving()
77 | } catch {
78 | stopExploring()
79 | return .Failure(createError("Could not begin receiving error"))
80 | }
81 |
82 | // Configure multicast socket
83 | // Bind to port without defining the interface to bind to the address INADDR_ANY (0.0.0.0). This prevents any address filtering which allows datagrams sent to the multicast group to be receives
84 | do {
85 | try multicastSocket.bindToPort(SSDPExplorer._multicastUDPPort)
86 | } catch {
87 | stopExploring()
88 | return .Failure(createError("Could not bind socket to multicast port"))
89 | }
90 |
91 | // Join multicast group to express interest to router of receiving multicast datagrams
92 | do {
93 | try multicastSocket.joinMulticastGroup(SSDPExplorer._multicastGroupAddress)
94 | } catch {
95 | stopExploring()
96 | return .Failure(createError("Could not join multicast group"))
97 | }
98 |
99 | do {
100 | try multicastSocket.beginReceiving()
101 | } catch {
102 | stopExploring()
103 | return .Failure(createError("Could not begin receiving error"))
104 | }
105 |
106 | _types = types
107 | for type in types {
108 | if let data = searchRequestData(forType: type) {
109 | // println(">>>> SENDING SEARCH REQUEST\n\(NSString(data: data, encoding: NSUTF8StringEncoding))")
110 | unicastSocket.sendData(data, toHost: SSDPExplorer._multicastGroupAddress, port: SSDPExplorer._multicastUDPPort, withTimeout: -1, tag: type.hashValue)
111 | }
112 | }
113 |
114 | return .Success
115 | }
116 |
117 | func stopExploring() {
118 | _multicastSocket?.close()
119 | _multicastSocket = nil
120 | _unicastSocket?.close()
121 | _unicastSocket = nil
122 | _types = []
123 | }
124 |
125 | private func searchRequestData(forType type: SSDPType) -> NSData? {
126 | var requestBody = [
127 | "M-SEARCH * HTTP/1.1",
128 | "HOST: \(SSDPExplorer._multicastGroupAddress):\(SSDPExplorer._multicastUDPPort)",
129 | "MAN: \"ssdp:discover\"",
130 | "ST: \(type.rawValue)",
131 | "MX: 3"]
132 |
133 | if let userAgent = AFHTTPRequestSerializer().valueForHTTPHeaderField("User-Agent") {
134 | requestBody += ["USER-AGENT: \(userAgent)\r\n\r\n\r\n"]
135 | }
136 |
137 | let requestBodyString = requestBody.joinWithSeparator("\r\n")
138 | return requestBodyString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
139 | }
140 |
141 | private func notifyDelegate(ofFailure error: NSError) {
142 | dispatch_async(dispatch_get_main_queue(), { [unowned self] () -> Void in
143 | self.delegate?.ssdpExplorer(self, didFailWithError: error)
144 | })
145 | }
146 |
147 | private func notifyDelegate(discovery: SSDPDiscovery, added: Bool) {
148 | dispatch_async(dispatch_get_main_queue(), { [unowned self] () -> Void in
149 | added ? self.delegate?.ssdpExplorer(self, didMakeDiscovery: discovery) : self.delegate?.ssdpExplorer(self, didRemoveDiscovery: discovery)
150 | })
151 | }
152 |
153 | private func handleSSDPMessage(messageType: SSDPMessageType, headers: [String: String]) {
154 | if let usnRawValue = headers["usn"],
155 | usn = UniqueServiceName(rawValue: usnRawValue),
156 | locationString = headers["location"],
157 | locationURL = NSURL(string: locationString),
158 | /// NT = Notification Type - SSDP discovered from device advertisements
159 | /// ST = Search Target - SSDP discovered as a result of using M-SEARCH requests
160 | ssdpTypeRawValue = (headers["st"] != nil ? headers["st"] : headers["nt"]),
161 | ssdpType = SSDPType(rawValue: ssdpTypeRawValue) where _types.indexOf(ssdpType) != nil {
162 | LogVerbose("SSDP response headers: \(headers)")
163 | let discovery = SSDPDiscovery(usn: usn, descriptionURL: locationURL, type: ssdpType)
164 | switch messageType {
165 | case .SearchResponse, .AvailableNotification, .UpdateNotification:
166 | notifyDelegate(discovery, added: true)
167 | case .UnavailableNotification:
168 | notifyDelegate(discovery, added: false)
169 | }
170 | }
171 | }
172 | }
173 |
174 | extension SSDPExplorer: GCDAsyncUdpSocketDelegate {
175 | @objc func udpSocket(sock: GCDAsyncUdpSocket!, didNotSendDataWithTag tag: Int, dueToError error: NSError!) {
176 | stopExploring()
177 |
178 | // this case should always have an error
179 | notifyDelegate(ofFailure: error ?? createError("Did not send SSDP message."))
180 | }
181 |
182 | @objc func udpSocketDidClose(sock: GCDAsyncUdpSocket!, withError error: NSError!) {
183 | if let error = error {
184 | notifyDelegate(ofFailure: error)
185 | }
186 | }
187 |
188 | @objc func udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!, withFilterContext filterContext: AnyObject!) {
189 | if let message = NSString(data: data, encoding: NSUTF8StringEncoding) as? String {
190 | // println({ () -> String in
191 | // let socketType = (sock === self._unicastSocket) ? "UNICAST" : "MULTICAST"
192 | // return "<<<< RECEIVED ON \(socketType) SOCKET\n\(message)"
193 | // }())
194 | var httpMethodLine: String?
195 | var headers = [String: String]()
196 | let headersRegularExpression = try? NSRegularExpression(pattern: "^([a-z0-9-]+): *(.+)$", options: [.CaseInsensitive, .AnchorsMatchLines])
197 | message.enumerateLines({ (line, stop) -> () in
198 | if httpMethodLine == nil {
199 | httpMethodLine = line
200 | } else {
201 | headersRegularExpression?.enumerateMatchesInString(line, options: [], range: NSRange(location: 0, length: line.characters.count), usingBlock: { (resultOptional: NSTextCheckingResult?, flags: NSMatchingFlags, stop: UnsafeMutablePointer) -> Void in
202 | if let result = resultOptional where result.numberOfRanges == 3 {
203 | let key = (line as NSString).substringWithRange(result.rangeAtIndex(1)).lowercaseString
204 | let value = (line as NSString).substringWithRange(result.rangeAtIndex(2))
205 | headers[key] = value
206 | }
207 | })
208 | }
209 | })
210 |
211 | if let httpMethodLine = httpMethodLine {
212 | let nts = headers["nts"]
213 | switch (httpMethodLine, nts) {
214 | case ("HTTP/1.1 200 OK", _):
215 | handleSSDPMessage(.SearchResponse, headers: headers)
216 | case ("NOTIFY * HTTP/1.1", .Some(let notificationType)) where notificationType == "ssdp:alive":
217 | handleSSDPMessage(.AvailableNotification, headers: headers)
218 | case ("NOTIFY * HTTP/1.1", .Some(let notificationType)) where notificationType == "ssdp:update":
219 | handleSSDPMessage(.UpdateNotification, headers: headers)
220 | case ("NOTIFY * HTTP/1.1", .Some(let notificationType)) where notificationType == "ssdp:byebye":
221 | headers["location"] = headers["host"] // byebye messages don't have a location
222 | handleSSDPMessage(.UnavailableNotification, headers: headers)
223 | default:
224 | return
225 | }
226 | }
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------