[ios11] MusicKit Sample (#191)
* [ios11] Added first progress of MusicKitSample - Added icons for sample - Finished UI design in Storyboard * [ios11][MusicKitSample] Added Utilities classes * [ios11][MusicKitSample] Added Controllers classes * [ios11][MusicKitSample] Added Views classes and finished sample * [ios11][MusicKitSample] Added Screenshots * [ios11][MusicKitSample] Added Metadata * [ios11][MusicKitSample] Added README * [ios11][MusicKitSample] Added .Net Standard 2.0 Console App to generate the MusicKit Developer Token
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<SampleMetadata>
|
||||
<ID>FB3F661C-0DFC-43D4-BE9E-6FBDE26349A4</ID>
|
||||
<IsFullApplication>false</IsFullApplication>
|
||||
<Level>Beginner</Level>
|
||||
<Tags>iOS11</Tags>
|
||||
<SupportedPlatforms>iOS</SupportedPlatforms>
|
||||
<Gallery>true</Gallery>
|
||||
<Brief>iOS 11 example to demonstrate the capabilities of MusicKit, including accessing the Apple Music catalog and playing songs, albums, and playlists using Media Player.</Brief>
|
||||
</SampleMetadata>
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MusicKitSample", "MusicKitSample\MusicKitSample.csproj", "{BCBBC179-9DD9-4630-B712-FFDB3BFBCAEF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|iPhoneSimulator = Debug|iPhoneSimulator
|
||||
Release|iPhone = Release|iPhone
|
||||
Release|iPhoneSimulator = Release|iPhoneSimulator
|
||||
Debug|iPhone = Debug|iPhone
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BCBBC179-9DD9-4630-B712-FFDB3BFBCAEF}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||
{BCBBC179-9DD9-4630-B712-FFDB3BFBCAEF}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||
{BCBBC179-9DD9-4630-B712-FFDB3BFBCAEF}.Release|iPhone.ActiveCfg = Release|iPhone
|
||||
{BCBBC179-9DD9-4630-B712-FFDB3BFBCAEF}.Release|iPhone.Build.0 = Release|iPhone
|
||||
{BCBBC179-9DD9-4630-B712-FFDB3BFBCAEF}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||
{BCBBC179-9DD9-4630-B712-FFDB3BFBCAEF}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||
{BCBBC179-9DD9-4630-B712-FFDB3BFBCAEF}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||
{BCBBC179-9DD9-4630-B712-FFDB3BFBCAEF}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,90 @@
|
|||
using Foundation;
|
||||
using UIKit;
|
||||
using MusicKitSample.Controllers;
|
||||
using System;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
// The UIApplicationDelegate for the application. This class is responsible for launching the
|
||||
// User Interface of the application, as well as listening (and optionally responding) to application events from iOS.
|
||||
[Register ("AppDelegate")]
|
||||
public class AppDelegate : UIApplicationDelegate
|
||||
{
|
||||
// class-level declarations
|
||||
|
||||
public override UIWindow Window {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// The instance of `AppleMusicManager` which handles making web
|
||||
// service calls to Apple Music Web Services.
|
||||
public AppleMusicManager AppleMusicManager { get; private set; }
|
||||
|
||||
// The instance of `MusicPlayerManager` which handles media playback.
|
||||
public MusicPlayerManager MusicPlayerManager { get; private set; }
|
||||
|
||||
// The instance of `AuthorizationManager` which is responsible
|
||||
// for managing authorization for the application.
|
||||
public AuthorizationManager AuthorizationManager { get; private set; }
|
||||
|
||||
// The instance of `MediaLibraryManager` which manages the
|
||||
// `MPPMediaPlaylist` this application creates.
|
||||
public MediaLibraryManager MediaLibraryManager { get; private set; }
|
||||
|
||||
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
|
||||
{
|
||||
// Override point for customization after application launch.
|
||||
// If not required for your application you can safely delete this method
|
||||
|
||||
AppleMusicManager = new AppleMusicManager ();
|
||||
MusicPlayerManager = new MusicPlayerManager ();
|
||||
AuthorizationManager = new AuthorizationManager (AppleMusicManager);
|
||||
MediaLibraryManager = new MediaLibraryManager (AuthorizationManager);
|
||||
|
||||
if (TopViewController (0) is AuthorizationTableViewController authorizationTableViewController)
|
||||
authorizationTableViewController.AuthorizationManager = AuthorizationManager;
|
||||
else
|
||||
throw new InvalidCastException ($"Unable to find expected {nameof (AuthorizationTableViewController)} in at TabBar Index 0");
|
||||
|
||||
if (TopViewController (1) is PlaylistTableViewController playlistTableViewController) {
|
||||
playlistTableViewController.AuthorizationManager = AuthorizationManager;
|
||||
playlistTableViewController.MediaLibraryManager = MediaLibraryManager;
|
||||
playlistTableViewController.MusicPlayerManager = MusicPlayerManager;
|
||||
} else
|
||||
throw new InvalidCastException ($"Unable to find expected {nameof (PlaylistTableViewController)} in at TabBar Index 1");
|
||||
|
||||
if (TopViewController (2) is PlayerViewController playerViewController)
|
||||
playerViewController.MusicPlayerManager = MusicPlayerManager;
|
||||
else
|
||||
throw new InvalidCastException ($"Unable to find expected {nameof (PlayerViewController)} in at TabBar Index 2");
|
||||
|
||||
if (TopViewController (3) is RecentlyPlayedTableViewController recentlyPlayedTableViewController) {
|
||||
recentlyPlayedTableViewController.AuthorizationManager = AuthorizationManager;
|
||||
recentlyPlayedTableViewController.AppleMusicManager = AppleMusicManager;
|
||||
recentlyPlayedTableViewController.MediaLibraryManager = MediaLibraryManager;
|
||||
recentlyPlayedTableViewController.MusicPlayerManager = MusicPlayerManager;
|
||||
} else
|
||||
throw new InvalidCastException ($"Unable to find expected {nameof (RecentlyPlayedTableViewController)} in at TabBar Index 3");
|
||||
|
||||
if (TopViewController (4) is MediaSearchTableViewController mediaSearchTableViewController) {
|
||||
mediaSearchTableViewController.AuthorizationManager = AuthorizationManager;
|
||||
mediaSearchTableViewController.MediaLibraryManager = MediaLibraryManager;
|
||||
mediaSearchTableViewController.MusicPlayerManager = MusicPlayerManager;
|
||||
} else
|
||||
throw new InvalidCastException ($"Unable to find expected {nameof (MediaSearchTableViewController)} in at TabBar Index 4");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UIViewController TopViewController (int index)
|
||||
{
|
||||
if (Window.RootViewController is UITabBarController tabBarController &&
|
||||
tabBarController.ViewControllers [index] is UINavigationController navigationController)
|
||||
return navigationController.TopViewController;
|
||||
else
|
||||
throw new InvalidCastException ("Unable to find expected View Controller in Main.storyboard.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
85
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Album.imageset/Contents.json
поставляемый
Normal file
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"images": [
|
||||
{
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"filename": "album_thumbnail.png",
|
||||
"scale": "1x",
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"filename": "album_thumbnail@2x.png",
|
||||
"scale": "2x",
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"filename": "album_thumbnail@3x.png",
|
||||
"scale": "3x",
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"scale": "1x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"scale": "2x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"subtype": "retina4",
|
||||
"scale": "2x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"scale": "3x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"scale": "1x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"scale": "2x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"screenWidth": "{130,145}",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"screenWidth": "{146,165}",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"scale": "1x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"scale": "2x",
|
||||
"idiom": "mac"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "xcode"
|
||||
}
|
||||
}
|
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Album.imageset/album_thumbnail.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 5.3 KiB |
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Album.imageset/album_thumbnail@2x.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 13 KiB |
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Album.imageset/album_thumbnail@3x.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 21 KiB |
|
@ -0,0 +1,157 @@
|
|||
{
|
||||
"images": [
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "3x"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "40x40",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "40x40",
|
||||
"scale": "3x"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "57x57",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "57x57",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "60x60",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone",
|
||||
"size": "60x60",
|
||||
"scale": "3x"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "29x29",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "29x29",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "40x40",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "40x40",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "50x50",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "50x50",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "72x72",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "72x72",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "76x76",
|
||||
"scale": "1x"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad",
|
||||
"size": "76x76",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "24x24",
|
||||
"idiom": "watch",
|
||||
"scale": "2x",
|
||||
"role": "notificationCenter",
|
||||
"subtype": "38mm"
|
||||
},
|
||||
{
|
||||
"size": "27.5x27.5",
|
||||
"idiom": "watch",
|
||||
"scale": "2x",
|
||||
"role": "notificationCenter",
|
||||
"subtype": "42mm"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"idiom": "watch",
|
||||
"role": "companionSettings",
|
||||
"scale": "2x"
|
||||
},
|
||||
{
|
||||
"size": "29x29",
|
||||
"idiom": "watch",
|
||||
"role": "companionSettings",
|
||||
"scale": "3x"
|
||||
},
|
||||
{
|
||||
"size": "40x40",
|
||||
"idiom": "watch",
|
||||
"scale": "2x",
|
||||
"role": "appLauncher",
|
||||
"subtype": "38mm"
|
||||
},
|
||||
{
|
||||
"size": "44x44",
|
||||
"idiom": "watch",
|
||||
"scale": "2x",
|
||||
"role": "longLook",
|
||||
"subtype": "42mm"
|
||||
},
|
||||
{
|
||||
"size": "86x86",
|
||||
"idiom": "watch",
|
||||
"scale": "2x",
|
||||
"role": "quickLook",
|
||||
"subtype": "38mm"
|
||||
},
|
||||
{
|
||||
"size": "98x98",
|
||||
"idiom": "watch",
|
||||
"scale": "2x",
|
||||
"role": "quickLook",
|
||||
"subtype": "42mm"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "xcode"
|
||||
}
|
||||
}
|
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Assets.imageset/Assets.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 249 B |
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Assets.imageset/Assets@2x.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 328 B |
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Assets.imageset/Assets@3x.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 270 B |
23
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Assets.imageset/Contents.json
поставляемый
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Assets.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Assets@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Assets@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
3617
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Backward.imageset/Backward.pdf
поставляемый
Normal file
12
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Backward.imageset/Contents.json
поставляемый
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Backward.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Configure.imageset/Configure.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 715 B |
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Configure.imageset/Configure@2x.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 1.7 KiB |
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Configure.imageset/Configure@3x.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 1.6 KiB |
23
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Configure.imageset/Contents.json
поставляемый
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Configure.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Configure@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Configure@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
12
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Forward.imageset/Contents.json
поставляемый
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Forward.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
3917
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Forward.imageset/Forward.pdf
поставляемый
Normal file
12
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Pause.imageset/Contents.json
поставляемый
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Pause.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
3714
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Pause.imageset/Pause.pdf
поставляемый
Normal file
12
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Play.imageset/Contents.json
поставляемый
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Play.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
3686
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Play.imageset/Play.pdf
поставляемый
Normal file
85
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Song.imageset/Contents.json
поставляемый
Normal file
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"images": [
|
||||
{
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"filename": "song_thumbnail.png",
|
||||
"scale": "1x",
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"filename": "song_thumbnail@2x.png",
|
||||
"scale": "2x",
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"filename": "song_thumbnail@3x.png",
|
||||
"scale": "3x",
|
||||
"idiom": "universal"
|
||||
},
|
||||
{
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"scale": "1x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"scale": "2x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"subtype": "retina4",
|
||||
"scale": "2x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"scale": "3x",
|
||||
"idiom": "iphone"
|
||||
},
|
||||
{
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"scale": "1x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"scale": "2x",
|
||||
"idiom": "ipad"
|
||||
},
|
||||
{
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"screenWidth": "{130,145}",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"screenWidth": "{146,165}",
|
||||
"scale": "2x",
|
||||
"idiom": "watch"
|
||||
},
|
||||
{
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"scale": "1x",
|
||||
"idiom": "mac"
|
||||
},
|
||||
{
|
||||
"scale": "2x",
|
||||
"idiom": "mac"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "xcode"
|
||||
}
|
||||
}
|
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Song.imageset/song_thumbnail.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 2.8 KiB |
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Song.imageset/song_thumbnail@2x.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 5.9 KiB |
Двоичные данные
ios11/MusicKitSample/MusicKitSample/Assets.xcassets/Song.imageset/song_thumbnail@3x.png
поставляемый
Normal file
После Ширина: | Высота: | Размер: 8.9 KiB |
|
@ -0,0 +1,175 @@
|
|||
using System;
|
||||
using Foundation;
|
||||
using MusicKitSample.Models;
|
||||
using MusicKitSample.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using UIKit;
|
||||
using System.Threading.Tasks;
|
||||
namespace MusicKitSample.Controllers
|
||||
{
|
||||
public class AppleMusicManager
|
||||
{
|
||||
#region Properties
|
||||
|
||||
// The instance of `URLSession` that is going to be used for making network calls.
|
||||
public NSUrlSession UrlSession { get; } = NSUrlSession.FromConfiguration (NSUrlSessionConfiguration.DefaultSessionConfiguration);
|
||||
|
||||
// The storefront id that is used when making Apple Music API calls.
|
||||
public string StorefrontId { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public AppleMusicManager ()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functionality
|
||||
|
||||
#region General Apple Music API Methods
|
||||
|
||||
public string FetchDeveloperToken ()
|
||||
{
|
||||
// ADAPT: YOU MUST IMPLEMENT THIS METHOD
|
||||
string developerAuthenticationToken = null;
|
||||
return developerAuthenticationToken;
|
||||
}
|
||||
|
||||
public async Task<MediaItem [] []> PerformAppleMusicCatalogSearchAsync (string term, string countryCode)
|
||||
{
|
||||
var developerToken = FetchDeveloperToken ();
|
||||
if (developerToken == null)
|
||||
throw new ArgumentNullException (nameof (developerToken), "Developer Token not configured. See README for more details.");
|
||||
|
||||
var urlRequest = AppleMusicRequestFactory.CreateSearchRequest (term, countryCode, developerToken);
|
||||
var dataTaskRequest = await UrlSession.CreateDataTaskAsync (urlRequest);
|
||||
var urlResponse = dataTaskRequest.Response as NSHttpUrlResponse;
|
||||
|
||||
if (urlResponse?.StatusCode != 200)
|
||||
return new MediaItem [0] [];
|
||||
|
||||
return ProcessMediaItemSections (dataTaskRequest.Data);
|
||||
}
|
||||
|
||||
public async Task<string> PerformAppleMusicStorefrontsLookupAsync (string regionCode)
|
||||
{
|
||||
var developerToken = FetchDeveloperToken ();
|
||||
if (developerToken == null)
|
||||
throw new ArgumentNullException (nameof (developerToken), "Developer Token not configured. See README for more details.");
|
||||
|
||||
var urlRequest = AppleMusicRequestFactory.CreateStorefrontsRequest (regionCode, developerToken);
|
||||
var dataTaskRequest = await UrlSession.CreateDataTaskAsync (urlRequest);
|
||||
var urlResponse = dataTaskRequest.Response as NSHttpUrlResponse;
|
||||
|
||||
if (urlResponse?.StatusCode != 200)
|
||||
return null;
|
||||
|
||||
return ProcessStorefront (dataTaskRequest.Data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Personalized Apple Music API Methods
|
||||
|
||||
public async Task<MediaItem []> PerformAppleMusicGetRecentlyPlayedAsync (string userToken)
|
||||
{
|
||||
var developerToken = FetchDeveloperToken ();
|
||||
if (developerToken == null)
|
||||
throw new ArgumentNullException (nameof (developerToken), "Developer Token not configured. See README for more details.");
|
||||
|
||||
var urlRequest = AppleMusicRequestFactory.CreateRecentlyPlayedRequest (developerToken, userToken);
|
||||
var dataTaskRequest = await UrlSession.CreateDataTaskAsync (urlRequest);
|
||||
var urlResponse = dataTaskRequest.Response as NSHttpUrlResponse;
|
||||
|
||||
if (urlResponse?.StatusCode != 200)
|
||||
return new MediaItem [0];
|
||||
|
||||
var jsonDictionary = NSJsonSerialization.Deserialize (dataTaskRequest.Data, NSJsonReadingOptions.AllowFragments, out NSError error) as NSDictionary;
|
||||
if (error != null)
|
||||
throw new NSErrorException (error);
|
||||
|
||||
var results = jsonDictionary [ResponseRootJsonKeys.Data] as NSArray ??
|
||||
throw new SerializationException (ResponseRootJsonKeys.Data);
|
||||
|
||||
return ProcessMediaItems (results);
|
||||
}
|
||||
|
||||
public async Task<string> PerformAppleMusicGetUserStorefrontAsync (string userToken)
|
||||
{
|
||||
var developerToken = FetchDeveloperToken ();
|
||||
if (developerToken == null)
|
||||
throw new ArgumentNullException (nameof (developerToken), "Developer Token not configured. See README for more details.");
|
||||
|
||||
var urlRequest = AppleMusicRequestFactory.CreateGetUserStorefrontRequest (developerToken, userToken);
|
||||
var dataTaskRequest = await UrlSession.CreateDataTaskAsync (urlRequest);
|
||||
var urlResponse = dataTaskRequest.Response as NSHttpUrlResponse;
|
||||
|
||||
if (urlResponse?.StatusCode != 200)
|
||||
return null;
|
||||
|
||||
return ProcessStorefront (dataTaskRequest.Data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functionality
|
||||
|
||||
MediaItem [] [] ProcessMediaItemSections (NSData json)
|
||||
{
|
||||
var jsonDictionary = NSJsonSerialization.Deserialize (json, NSJsonReadingOptions.AllowFragments, out NSError error) as NSDictionary;
|
||||
|
||||
if (error != null)
|
||||
throw new NSErrorException (error);
|
||||
|
||||
var results = jsonDictionary [ResponseRootJsonKeys.Results] as NSDictionary ??
|
||||
throw new SerializationException (ResponseRootJsonKeys.Results);
|
||||
|
||||
var mediaItems = new List<MediaItem []> ();
|
||||
var songsDictionary = results [ResourceTypeJsonKeys.Songs] as NSDictionary;
|
||||
var dataArray = songsDictionary? [ResponseRootJsonKeys.Data] as NSArray;
|
||||
|
||||
if (dataArray != null)
|
||||
mediaItems.Add (ProcessMediaItems (dataArray));
|
||||
|
||||
var albumsDictionary = results [ResourceTypeJsonKeys.Albums] as NSDictionary;
|
||||
dataArray = albumsDictionary? [ResponseRootJsonKeys.Data] as NSArray;
|
||||
|
||||
if (dataArray != null)
|
||||
mediaItems.Add (ProcessMediaItems (dataArray));
|
||||
|
||||
return mediaItems.ToArray ();
|
||||
}
|
||||
|
||||
MediaItem [] ProcessMediaItems (NSArray json)
|
||||
{
|
||||
var mediaItems = new MediaItem [json.Count];
|
||||
|
||||
for (nuint i = 0; i < json.Count; i++)
|
||||
mediaItems [i] = MediaItem.From (json.GetItem<NSDictionary> (i));
|
||||
|
||||
return mediaItems;
|
||||
}
|
||||
|
||||
string ProcessStorefront (NSData json)
|
||||
{
|
||||
var jsonDictionary = NSJsonSerialization.Deserialize (json, NSJsonReadingOptions.AllowFragments, out NSError error) as NSDictionary;
|
||||
|
||||
if (error != null)
|
||||
throw new NSErrorException (error);
|
||||
|
||||
var dataArray = jsonDictionary [ResponseRootJsonKeys.Data] as NSArray ??
|
||||
throw new SerializationException (ResponseRootJsonKeys.Data);
|
||||
var id = dataArray?.GetItem<NSDictionary> (0) [ResourceJsonKeys.Id]?.ToString () ??
|
||||
throw new SerializationException (ResourceJsonKeys.Id);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
using System;
|
||||
using StoreKit;
|
||||
using System.Collections.Generic;
|
||||
using Foundation;
|
||||
using MediaPlayer;
|
||||
using ObjCRuntime;
|
||||
namespace MusicKitSample.Controllers
|
||||
{
|
||||
public class AuthorizationDataSource
|
||||
{
|
||||
[Native]
|
||||
enum SectionTypes : long
|
||||
{
|
||||
MediaLibraryAuthorizationStatus = 0,
|
||||
CloudServiceAuthorizationStatus,
|
||||
RequestCapabilities
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
public AuthorizationManager AuthorizationManager { get; private set; }
|
||||
public SKCloudServiceCapability [] Capabilities { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public AuthorizationDataSource (AuthorizationManager authorizationManager)
|
||||
{
|
||||
AuthorizationManager = authorizationManager;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functionality
|
||||
|
||||
#region Data Source Methods
|
||||
|
||||
public int NumberOfSections ()
|
||||
{
|
||||
// There is always a section for the displaying
|
||||
// AuthorizationStatus from `SKCloudServiceController`
|
||||
// and `MPMediaLibrary`.
|
||||
var section = 2;
|
||||
|
||||
// If we have capabilities to display from
|
||||
// RequestCapabilities from SKCloudServiceController.
|
||||
if (SKCloudServiceController.AuthorizationStatus != SKCloudServiceAuthorizationStatus.Authorized)
|
||||
return section;
|
||||
|
||||
var capabilities = new List<SKCloudServiceCapability> ();
|
||||
var cloudServiceCapabilities = AuthorizationManager.CloudServiceCapabilities;
|
||||
|
||||
if (cloudServiceCapabilities.HasFlag (SKCloudServiceCapability.AddToCloudMusicLibrary))
|
||||
capabilities.Add (SKCloudServiceCapability.AddToCloudMusicLibrary);
|
||||
|
||||
if (cloudServiceCapabilities.HasFlag (SKCloudServiceCapability.MusicCatalogPlayback))
|
||||
capabilities.Add (SKCloudServiceCapability.MusicCatalogPlayback);
|
||||
|
||||
if (cloudServiceCapabilities.HasFlag (SKCloudServiceCapability.MusicCatalogSubscriptionEligible))
|
||||
capabilities.Add (SKCloudServiceCapability.MusicCatalogSubscriptionEligible);
|
||||
|
||||
Capabilities = capabilities.ToArray ();
|
||||
|
||||
return ++section;
|
||||
}
|
||||
|
||||
public int NumberOfItems (long section)
|
||||
{
|
||||
var sectionType = (SectionTypes)section;
|
||||
|
||||
switch (sectionType) {
|
||||
case SectionTypes.MediaLibraryAuthorizationStatus:
|
||||
case SectionTypes.CloudServiceAuthorizationStatus:
|
||||
return 1;
|
||||
case SectionTypes.RequestCapabilities:
|
||||
return Capabilities.Length;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public string SectionTitle (long section)
|
||||
{
|
||||
var sectionType = (SectionTypes)section;
|
||||
|
||||
switch (sectionType) {
|
||||
case SectionTypes.MediaLibraryAuthorizationStatus:
|
||||
return "MPMediaLibrary";
|
||||
case SectionTypes.CloudServiceAuthorizationStatus:
|
||||
return "SKCloudServiceController";
|
||||
case SectionTypes.RequestCapabilities:
|
||||
return "Capabilities";
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public string StringForItem (NSIndexPath indexPath)
|
||||
{
|
||||
var sectionType = (SectionTypes)indexPath.Section;
|
||||
|
||||
switch (sectionType) {
|
||||
case SectionTypes.MediaLibraryAuthorizationStatus:
|
||||
return GetStringValue (MPMediaLibrary.AuthorizationStatus);
|
||||
case SectionTypes.CloudServiceAuthorizationStatus:
|
||||
return GetStringValue (SKCloudServiceController.AuthorizationStatus);
|
||||
case SectionTypes.RequestCapabilities:
|
||||
return GetStringValue (Capabilities [indexPath.Row]);
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functionality
|
||||
|
||||
string GetStringValue (SKCloudServiceAuthorizationStatus authorizationStatus)
|
||||
{
|
||||
switch (authorizationStatus) {
|
||||
case SKCloudServiceAuthorizationStatus.NotDetermined:
|
||||
return "Not Determined";
|
||||
case SKCloudServiceAuthorizationStatus.Denied:
|
||||
case SKCloudServiceAuthorizationStatus.Restricted:
|
||||
case SKCloudServiceAuthorizationStatus.Authorized:
|
||||
return authorizationStatus.ToString ("f");
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
string GetStringValue (MPMediaLibraryAuthorizationStatus authorizationStatus)
|
||||
{
|
||||
switch (authorizationStatus) {
|
||||
case MPMediaLibraryAuthorizationStatus.NotDetermined:
|
||||
return "Not Determined";
|
||||
case MPMediaLibraryAuthorizationStatus.Denied:
|
||||
case MPMediaLibraryAuthorizationStatus.Restricted:
|
||||
case MPMediaLibraryAuthorizationStatus.Authorized:
|
||||
return authorizationStatus.ToString ("f");
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
string GetStringValue (SKCloudServiceCapability capability)
|
||||
{
|
||||
switch (capability) {
|
||||
case SKCloudServiceCapability.MusicCatalogPlayback:
|
||||
return "Music Catalog Playback";
|
||||
case SKCloudServiceCapability.MusicCatalogSubscriptionEligible:
|
||||
return "Music Catalog Subscription Eligible";
|
||||
case SKCloudServiceCapability.AddToCloudMusicLibrary:
|
||||
return "Add To Cloud Music Library";
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
using System;
|
||||
|
||||
using Foundation;
|
||||
using StoreKit;
|
||||
using System.Threading.Tasks;
|
||||
using UIKit;
|
||||
using MediaPlayer;
|
||||
|
||||
namespace MusicKitSample.Controllers
|
||||
{
|
||||
public class AuthorizationManager : NSObject
|
||||
{
|
||||
#region Fields
|
||||
|
||||
NSObject CloudServiceCapabilitiesDidChangeNotificationToken;
|
||||
NSObject StorefrontCountryCodeDidChangeNotificationToken;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Types
|
||||
|
||||
// Notification that is posted whenever there is a change in the capabilities or Storefront identifier of the `SKCloudServiceController`.
|
||||
public static NSString CloudServiceDidUpdateNotification { get; } = new NSString ("cloudServiceDidUpdateNotification");
|
||||
|
||||
// Notification that is posted whenever there is a change in the authorization status that other parts of the sample should respond to.
|
||||
public static NSString AuthorizationDidUpdateNotification { get; } = new NSString ("authorizationDidUpdateNotification");
|
||||
|
||||
// The `UserDefaults` key for storing and retrieving the Music User Token associated with the currently signed in iTunes Store account.
|
||||
public static NSString UserTokenUserDefaultsKey { get; } = new NSString ("UserTokenUserDefaultsKey");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
// The instance of `SKCloudServiceController` that will be used for querying the available `SKCloudServiceCapability` and Storefront Identifier.
|
||||
public SKCloudServiceController CloudServiceController { get; set; }
|
||||
|
||||
// The instance of `AppleMusicManager` that will be used for querying storefront information and user token.
|
||||
public AppleMusicManager AppleMusicManager { get; set; }
|
||||
|
||||
// The current set of `SKCloudServiceCapability` that the sample can currently use.
|
||||
public SKCloudServiceCapability CloudServiceCapabilities { get; set; }
|
||||
|
||||
// The current set of two letter country code associated with the currently authenticated iTunes Store account.
|
||||
public string CloudServiceStorefrontCountryCode { get; set; }
|
||||
|
||||
// The Music User Token associated with the currently signed in iTunes Store account.
|
||||
public string UserToken { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public AuthorizationManager (AppleMusicManager appleMusicManager)
|
||||
{
|
||||
AppleMusicManager = appleMusicManager;
|
||||
CloudServiceController = new SKCloudServiceController ();
|
||||
CloudServiceCapabilities = new SKCloudServiceCapability ();
|
||||
|
||||
/*
|
||||
* It is important that your application listens to the
|
||||
* `CloudServiceCapabilitiesDidChangeNotification` and
|
||||
* `StorefrontCountryCodeDidChangeNotification` notifications
|
||||
* so that your application can update its state and
|
||||
* functionality when these values change if needed.
|
||||
*/
|
||||
var notificationCenter = NSNotificationCenter.DefaultCenter;
|
||||
CloudServiceCapabilitiesDidChangeNotificationToken = notificationCenter.AddObserver (SKCloudServiceController.CloudServiceCapabilitiesDidChangeNotification,
|
||||
async (obj) => await RequestCloudServiceCapabilitiesAsync ());
|
||||
|
||||
if (UIDevice.CurrentDevice.CheckSystemVersion (11, 0))
|
||||
StorefrontCountryCodeDidChangeNotificationToken = notificationCenter.AddObserver (SKCloudServiceController.StorefrontCountryCodeDidChangeNotification,
|
||||
async (obj) => await RequestStorefrontCountryCodeAsync ());
|
||||
|
||||
/*
|
||||
* If the application has already been authorized in a
|
||||
* previous run or manually by the user then it can
|
||||
* request the current set of `SKCloudServiceCapability`
|
||||
* and Storefront Identifier.
|
||||
*/
|
||||
|
||||
if (SKCloudServiceController.AuthorizationStatus == SKCloudServiceAuthorizationStatus.Authorized) {
|
||||
Task.Factory.StartNew (async () => {
|
||||
await RequestCloudServiceCapabilitiesAsync ();
|
||||
|
||||
// Retrieve the Music User Token for use in the application
|
||||
// if it was stored from a previous run.
|
||||
if (NSUserDefaults.StandardUserDefaults.StringForKey (UserTokenUserDefaultsKey) is string token) {
|
||||
UserToken = token;
|
||||
await RequestStorefrontCountryCodeAsync ();
|
||||
} else // The token was not stored previously then request one.
|
||||
await RequestUserTokenAsync ();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Life Cycle
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
// Remove all notification observers.
|
||||
var notificationCenter = NSNotificationCenter.DefaultCenter;
|
||||
notificationCenter.RemoveObserver (CloudServiceCapabilitiesDidChangeNotificationToken);
|
||||
|
||||
if (UIDevice.CurrentDevice.CheckSystemVersion (11, 0))
|
||||
notificationCenter.RemoveObserver (StorefrontCountryCodeDidChangeNotificationToken);
|
||||
|
||||
CloudServiceCapabilitiesDidChangeNotificationToken = StorefrontCountryCodeDidChangeNotificationToken = null;
|
||||
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functionality
|
||||
|
||||
#region Authorization Request Methods
|
||||
|
||||
public async Task RequestCloudServiceAuthorizationAsync ()
|
||||
{
|
||||
/*
|
||||
* An application should only ever call
|
||||
* `SKCloudServiceController.RequestAuthorization` when
|
||||
* their current authorization is
|
||||
* `SKCloudServiceAuthorizationStatus.NotDetermined`
|
||||
*/
|
||||
if (SKCloudServiceController.AuthorizationStatus != SKCloudServiceAuthorizationStatus.NotDetermined)
|
||||
return;
|
||||
|
||||
/*
|
||||
* `SKCloudServiceController.RequestAuthorizationAsync ()`
|
||||
* triggers a prompt for the user asking if they wish to
|
||||
* allow the application that requested authorization
|
||||
* access to the device's cloud services information.
|
||||
* This allows the application to query information such
|
||||
* as the what capabilities the currently authenticated
|
||||
* iTunes Store account has and if the account is
|
||||
* eligible for an Apple Music Subscription Trial.
|
||||
*
|
||||
* This prompt will also include the value provided in
|
||||
* the application's Info.plist for the
|
||||
* `NSAppleMusicUsageDescription` key. This usage
|
||||
* description should reflect what the application
|
||||
* intends to use this access for.
|
||||
*/
|
||||
var authorizationStatus = await SKCloudServiceController.RequestAuthorizationAsync ();
|
||||
|
||||
switch (authorizationStatus) {
|
||||
case SKCloudServiceAuthorizationStatus.Authorized:
|
||||
await RequestCloudServiceCapabilitiesAsync ();
|
||||
await RequestUserTokenAsync ();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
InvokeOnMainThread (() => NSNotificationCenter.DefaultCenter.PostNotificationName (AuthorizationDidUpdateNotification, null));
|
||||
}
|
||||
|
||||
public async Task RequestMediaLibraryAuthorizationAsync ()
|
||||
{
|
||||
/*
|
||||
* An application should only ever call
|
||||
* `MPMediaLibrary.AuthorizationStatus` when their current
|
||||
* authorization is `MPMediaLibraryAuthorizationStatus.NotDetermined`
|
||||
*/
|
||||
if (MPMediaLibrary.AuthorizationStatus != MPMediaLibraryAuthorizationStatus.NotDetermined)
|
||||
return;
|
||||
|
||||
/*
|
||||
* `MPMediaLibrary.RequestAuthorizationAsync ()` triggers a
|
||||
* prompt for the user asking if they wish to allow the
|
||||
* application that requested authorization access to
|
||||
* the device's media library.
|
||||
*
|
||||
* This prompt will also include the value provided in
|
||||
* the application's Info.plist for the
|
||||
* `NSAppleMusicUsageDescription` key. This usage
|
||||
* description should reflect what the application
|
||||
* intends to use this access for.
|
||||
*/
|
||||
await MPMediaLibrary.RequestAuthorizationAsync ();
|
||||
InvokeOnMainThread (() => NSNotificationCenter.DefaultCenter.PostNotificationName (CloudServiceDidUpdateNotification, null));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region `SKCloudServiceController` Related Methods
|
||||
|
||||
async Task RequestCloudServiceCapabilitiesAsync ()
|
||||
{
|
||||
CloudServiceCapabilities = await CloudServiceController.RequestCapabilitiesAsync ();
|
||||
InvokeOnMainThread (() => NSNotificationCenter.DefaultCenter.PostNotificationName (CloudServiceDidUpdateNotification, null));
|
||||
}
|
||||
|
||||
async Task RequestStorefrontCountryCodeAsync ()
|
||||
{
|
||||
string countryCode;
|
||||
|
||||
if (SKCloudServiceController.AuthorizationStatus == SKCloudServiceAuthorizationStatus.Authorized) {
|
||||
if (UIDevice.CurrentDevice.CheckSystemVersion (11, 0)) {
|
||||
/*
|
||||
* On iOS 11.0 or later, if the
|
||||
* `SKCloudServiceController.authorizationStatus()`
|
||||
* is `.authorized` then you can request the
|
||||
* storefront country code.
|
||||
*/
|
||||
countryCode = (await CloudServiceController.RequestStorefrontCountryCodeAsync ()).ToString ();
|
||||
} else {
|
||||
countryCode = await AppleMusicManager.PerformAppleMusicGetUserStorefrontAsync (UserToken);
|
||||
}
|
||||
} else {
|
||||
countryCode = await DetermineRegion ();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace (countryCode))
|
||||
throw new ArgumentNullException (nameof (countryCode), "Unexpected value from SKCloudServiceController for storefront country code.");
|
||||
|
||||
CloudServiceStorefrontCountryCode = countryCode;
|
||||
|
||||
InvokeOnMainThread (() => NSNotificationCenter.DefaultCenter.PostNotificationName (CloudServiceDidUpdateNotification, null));
|
||||
}
|
||||
|
||||
async Task RequestUserTokenAsync ()
|
||||
{
|
||||
var developerToken = AppleMusicManager.FetchDeveloperToken ();
|
||||
if (developerToken == null)
|
||||
throw new ArgumentNullException (nameof (developerToken), "Developer Token not configured. See README for more details.");
|
||||
|
||||
if (SKCloudServiceController.AuthorizationStatus != SKCloudServiceAuthorizationStatus.Authorized)
|
||||
return;
|
||||
|
||||
string token;
|
||||
|
||||
if (UIDevice.CurrentDevice.CheckSystemVersion (11, 0))
|
||||
token = await CloudServiceController.RequestUserTokenAsync (developerToken);
|
||||
else
|
||||
token = await CloudServiceController.RequestPersonalizationTokenAsync (developerToken);
|
||||
|
||||
if (string.IsNullOrWhiteSpace (token)) {
|
||||
Console.WriteLine ("Unexpected value from SKCloudServiceController for user token.");
|
||||
return;
|
||||
}
|
||||
|
||||
UserToken = token;
|
||||
|
||||
// Store the Music User Token for future use in your application.
|
||||
var userDefaults = NSUserDefaults.StandardUserDefaults;
|
||||
userDefaults.SetString (token, UserTokenUserDefaultsKey);
|
||||
userDefaults.Synchronize ();
|
||||
|
||||
if (string.IsNullOrWhiteSpace (CloudServiceStorefrontCountryCode))
|
||||
await RequestStorefrontCountryCodeAsync ();
|
||||
|
||||
InvokeOnMainThread (() => NSNotificationCenter.DefaultCenter.PostNotificationName (CloudServiceDidUpdateNotification, null));
|
||||
}
|
||||
|
||||
async Task<string> DetermineRegion ()
|
||||
{
|
||||
/*
|
||||
* On other versions of iOS or when
|
||||
* `SKCloudServiceController.AuthorizationStatus` is not
|
||||
* `SKCloudServiceAuthorizationStatus.Authorized`, your
|
||||
* application should use a combination of the device's
|
||||
* `Locale.current.regionCode` and the Apple Music API
|
||||
* to make an approximation of the storefront to use.
|
||||
*/
|
||||
var currentRegionCode = NSLocale.CurrentLocale.CountryCode.ToLower ();
|
||||
return await AppleMusicManager.PerformAppleMusicStorefrontsLookupAsync (currentRegionCode);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using System.Threading.Tasks;
|
||||
namespace MusicKitSample.Controllers
|
||||
{
|
||||
public class ImageCacheManager
|
||||
{
|
||||
#region Fields
|
||||
|
||||
NSCache imageCache;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ImageCacheManager ()
|
||||
{
|
||||
imageCache = new NSCache {
|
||||
Name = "ImageCacheManager",
|
||||
CountLimit = 20, // Max 20 images in memory.
|
||||
TotalCostLimit = 10 * 1024 * 1024 // Max 10MB used.
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functionality - Image Caching Methods
|
||||
|
||||
public UIImage GetCachedImage (NSUrl url)
|
||||
{
|
||||
if (url != null)
|
||||
return imageCache.ObjectForKey (new NSString (url.AbsoluteString)) as UIImage;
|
||||
else
|
||||
return new UIImage ();
|
||||
}
|
||||
|
||||
public async Task<UIImage> FetchImage (NSUrl url)
|
||||
{
|
||||
var dataTaskRequest = await NSUrlSession.SharedSession.CreateDataTaskAsync (url);
|
||||
var urlResponse = dataTaskRequest.Response as NSHttpUrlResponse;
|
||||
|
||||
if (urlResponse?.StatusCode != 200 || dataTaskRequest.Data == null) {
|
||||
// Your application should handle these errors
|
||||
// appropriately depending on the kind of error.
|
||||
return null;
|
||||
}
|
||||
|
||||
var image = UIImage.LoadFromData (dataTaskRequest.Data);
|
||||
|
||||
if (image != null) {
|
||||
imageCache.SetObjectforKey (image, new NSString (url.AbsoluteString));
|
||||
return image;
|
||||
} else
|
||||
return new UIImage ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using Foundation;
|
||||
using MediaPlayer;
|
||||
using System.Threading.Tasks;
|
||||
using UIKit;
|
||||
namespace MusicKitSample.Controllers
|
||||
{
|
||||
public class MediaLibraryManager : NSObject
|
||||
{
|
||||
#region Types
|
||||
|
||||
// The Key for the `UserDefaults` value representing the UUID of
|
||||
// the Playlist this sample creates.
|
||||
public static readonly string playlistUuidKey = "playlistUUIDKey";
|
||||
|
||||
// Notification that is posted whenever the contents of the
|
||||
// device's Media Library changed.
|
||||
public static readonly NSString LibraryDidUpdate = new NSString ("libraryDidUpdate");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
NSObject authorizationDidUpdateNotificationToken;
|
||||
NSObject didChangeNotificationToken;
|
||||
NSObject willEnterForegroundNotificationToken;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
// The instance of `AuthorizationManager` used for looking up
|
||||
// the current device's Media Library and Cloud Services
|
||||
// authorization status.
|
||||
public AuthorizationManager AuthorizationManager { get; set; }
|
||||
|
||||
// The instance of `MPMediaPlaylist` that corresponds to the
|
||||
// playlist created by this sample in the current device's Media
|
||||
// Library.
|
||||
public MPMediaPlaylist MediaPlaylist { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public MediaLibraryManager (AuthorizationManager authorizationManager)
|
||||
{
|
||||
AuthorizationManager = authorizationManager;
|
||||
|
||||
// Add the notification observers needed to respond to
|
||||
// events from the `AuthorizationManager`,
|
||||
// `MPMediaLibrary` and `UIApplication`.
|
||||
var notificationCenter = NSNotificationCenter.DefaultCenter;
|
||||
authorizationDidUpdateNotificationToken = notificationCenter.AddObserver (AuthorizationManager.AuthorizationDidUpdateNotification,
|
||||
HandleAuthorizationManagerAuthorizationDidUpdateNotification,
|
||||
null);
|
||||
didChangeNotificationToken = notificationCenter.AddObserver (MPMediaLibrary.DidChangeNotification,
|
||||
HandleMediaLibraryDidChangeNotification,
|
||||
null);
|
||||
willEnterForegroundNotificationToken = notificationCenter.AddObserver (UIApplication.WillEnterForegroundNotification,
|
||||
HandleMediaLibraryDidChangeNotification,
|
||||
null);
|
||||
|
||||
HandleAuthorizationManagerAuthorizationDidUpdateNotification (null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Life Cycle
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
// Remove all notification observers.
|
||||
var notificationCenter = NSNotificationCenter.DefaultCenter;
|
||||
notificationCenter.RemoveObserver (authorizationDidUpdateNotificationToken);
|
||||
notificationCenter.RemoveObserver (didChangeNotificationToken);
|
||||
notificationCenter.RemoveObserver (willEnterForegroundNotificationToken);
|
||||
|
||||
authorizationDidUpdateNotificationToken = didChangeNotificationToken = willEnterForegroundNotificationToken = null;
|
||||
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functionality
|
||||
|
||||
async Task CreatePlaylistIfNeededAsync ()
|
||||
{
|
||||
if (MediaPlaylist != null)
|
||||
return;
|
||||
|
||||
// To create a new playlist or lookup a playlist there
|
||||
// are several steps you need to do.
|
||||
NSUuid playlistUuid;
|
||||
MPMediaPlaylistCreationMetadata playlistCreationMetadata = null;
|
||||
var userDefaults = NSUserDefaults.StandardUserDefaults;
|
||||
|
||||
if (userDefaults.StringForKey (playlistUuidKey) is string playlistUuidString) {
|
||||
// In this case, the sample already created a playlist in
|
||||
// a previous run. In this case we lookup the UUID that
|
||||
// was used before.
|
||||
playlistUuid = new NSUuid (playlistUuidString);
|
||||
} else {
|
||||
// Create an instance of `UUID` to identify the new playlist.
|
||||
playlistUuid = new NSUuid ();
|
||||
|
||||
// Create an instance of `MPMediaPlaylistCreationMetadata`,
|
||||
// this represents the metadata to associate with the new playlist.
|
||||
playlistCreationMetadata = new MPMediaPlaylistCreationMetadata ("Test Playlist") {
|
||||
DescriptionText = $"This playlist was created using {NSBundle.MainBundle.InfoDictionary ["CFBundleName"]} to demonstrate how to use the Apple Music APIs"
|
||||
};
|
||||
|
||||
// Store the `UUID` that the sample will use for looking
|
||||
// up the playlist in the future.
|
||||
userDefaults.SetString (playlistUuid.AsString (), playlistUuidKey);
|
||||
userDefaults.Synchronize ();
|
||||
}
|
||||
|
||||
// Request the new or existing playlist from the device.
|
||||
MediaPlaylist = await MPMediaLibrary.DefaultMediaLibrary.GetPlaylistAsync (playlistUuid, playlistCreationMetadata);
|
||||
|
||||
InvokeOnMainThread (() => NSNotificationCenter.DefaultCenter.PostNotificationName (LibraryDidUpdate, null));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Playlist Modification Method
|
||||
|
||||
public async Task AddItemAsync (string id)
|
||||
{
|
||||
if (MediaPlaylist == null)
|
||||
throw new ArgumentNullException (nameof (MediaPlaylist), "Playlist has not been created");
|
||||
|
||||
await MediaPlaylist.AddItemAsync (id);
|
||||
|
||||
InvokeOnMainThread (() => NSNotificationCenter.DefaultCenter.PostNotificationName (LibraryDidUpdate, null));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notification Observing Methods
|
||||
|
||||
void HandleAuthorizationManagerAuthorizationDidUpdateNotification (NSNotification notification)
|
||||
{
|
||||
if (MPMediaLibrary.AuthorizationStatus == MPMediaLibraryAuthorizationStatus.Authorized)
|
||||
Task.Factory.StartNew (async () => await CreatePlaylistIfNeededAsync ());
|
||||
}
|
||||
|
||||
void HandleMediaLibraryDidChangeNotification (NSNotification notification)
|
||||
{
|
||||
if (MPMediaLibrary.AuthorizationStatus == MPMediaLibraryAuthorizationStatus.Authorized)
|
||||
Task.Factory.StartNew (async () => {
|
||||
await CreatePlaylistIfNeededAsync ();
|
||||
InvokeOnMainThread (() => NSNotificationCenter.DefaultCenter.PostNotificationName (LibraryDidUpdate, null));
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
using System;
|
||||
using Foundation;
|
||||
using MediaPlayer;
|
||||
namespace MusicKitSample.Controllers
|
||||
{
|
||||
public class MusicPlayerManager : NSObject
|
||||
{
|
||||
#region Types
|
||||
|
||||
// Notification that is fired when there is an update to the
|
||||
// playback state or currently playing asset in
|
||||
// `MPMusicPlayerController`.
|
||||
public static readonly NSString DidUpdateState = new NSString ("didUpdateState");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
NSObject nowPlayingItemDidChangeNotificationToken;
|
||||
NSObject playbackStateDidChangeNotificationChangeToken;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/*
|
||||
* The instance of `MPMusicPlayerController` that is used for
|
||||
* playing back titles from either the device media library
|
||||
* or from the Apple Music Catalog.
|
||||
*/
|
||||
public MPMusicPlayerController MusicPlayerController { get; } = MPMusicPlayerController.SystemMusicPlayer;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public MusicPlayerManager ()
|
||||
{
|
||||
/*
|
||||
* It is important to call
|
||||
* `MPMusicPlayerController.BeginGeneratingPlaybackNotifications()`
|
||||
* so that playback notifications are generated and other parts
|
||||
* of the can update their state if needed.
|
||||
*/
|
||||
MusicPlayerController.BeginGeneratingPlaybackNotifications ();
|
||||
|
||||
var notificationCenter = NSNotificationCenter.DefaultCenter;
|
||||
nowPlayingItemDidChangeNotificationToken = notificationCenter.AddObserver (MPMusicPlayerController.NowPlayingItemDidChangeNotification,
|
||||
HandleMusicPlayerControllerNowPlayingItemDidChange,
|
||||
null);
|
||||
playbackStateDidChangeNotificationChangeToken = notificationCenter.AddObserver (MPMusicPlayerController.PlaybackStateDidChangeNotification,
|
||||
HandleMusicPlayerControllerPlaybackStateDidChange,
|
||||
null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Life Cycle
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
/*
|
||||
* It is important to call
|
||||
* `MPMusicPlayerController.endGeneratingPlaybackNotifications()`
|
||||
* so that playback notifications are no longer generated.
|
||||
*/
|
||||
MusicPlayerController.EndGeneratingPlaybackNotifications ();
|
||||
|
||||
// Remove all notification observers.
|
||||
var notificationCenter = NSNotificationCenter.DefaultCenter;
|
||||
notificationCenter.RemoveObserver (nowPlayingItemDidChangeNotificationToken);
|
||||
notificationCenter.RemoveObserver (playbackStateDidChangeNotificationChangeToken);
|
||||
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Playback Loading Methods
|
||||
|
||||
public void BeginPlayback (MPMediaItemCollection itemCollection)
|
||||
{
|
||||
MusicPlayerController.SetQueue (itemCollection);
|
||||
MusicPlayerController.Play ();
|
||||
}
|
||||
|
||||
public void BeginPlayback (string itemId)
|
||||
{
|
||||
MusicPlayerController.SetQueue (new [] { itemId });
|
||||
MusicPlayerController.Play ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Playback Control Methods
|
||||
|
||||
public void TogglePlayPause ()
|
||||
{
|
||||
if (MusicPlayerController.PlaybackState == MPMusicPlaybackState.Playing)
|
||||
MusicPlayerController.Pause ();
|
||||
else
|
||||
MusicPlayerController.Play ();
|
||||
}
|
||||
|
||||
public void SkipToNextItem () => MusicPlayerController.SkipToNextItem ();
|
||||
|
||||
public void SkipBackToBeginningOrPreviousItem ()
|
||||
{
|
||||
if (MusicPlayerController.CurrentPlaybackTime < 5) {
|
||||
// If the currently playing `MPMediaItem` is less than 5 seconds
|
||||
// into playback then skip to the previous item.
|
||||
MusicPlayerController.SkipToPreviousItem ();
|
||||
} else {
|
||||
// Otherwise skip back to the beginning of the currently
|
||||
// playing `MPMediaItem`.
|
||||
MusicPlayerController.SkipToBeginning ();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notification Observing Methods
|
||||
|
||||
void HandleMusicPlayerControllerNowPlayingItemDidChange (NSNotification notification) =>
|
||||
NSNotificationCenter.DefaultCenter.PostNotificationName (DidUpdateState, null);
|
||||
|
||||
void HandleMusicPlayerControllerPlaybackStateDidChange (NSNotification notification) =>
|
||||
NSNotificationCenter.DefaultCenter.PostNotificationName (DidUpdateState, null);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>MusicKitSample</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.xamarin.MusicKitSample</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>11.0</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>kTCCServiceMediaLibrary</key>
|
||||
<string>Demonstrating using StoreKit and MediaPlayer APIs.</string>
|
||||
<key>NSAppleMusicUsageDescription</key>
|
||||
<string>Demonstrating using MusicKit API.</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS" />
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530" />
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb" />
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok" />
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600" />
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" />
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite" />
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder" />
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375" />
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -0,0 +1,15 @@
|
|||
using UIKit;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
public class Application
|
||||
{
|
||||
// This is the main entry point of the application.
|
||||
static void Main (string [] args)
|
||||
{
|
||||
// if you want to use a different Application Delegate class from "AppDelegate"
|
||||
// you can specify it here.
|
||||
UIApplication.Main (args, null, "AppDelegate");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,543 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13196" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="G6S-L2-L7g">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13173"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Player-->
|
||||
<scene sceneID="E9T-Gf-vjt">
|
||||
<objects>
|
||||
<viewController id="MSK-cM-e61" customClass="PlayerViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="NNN-oa-KF2"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="ELQ-Ug-Qpc"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="LkG-6K-ggf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="UCf-TJ-T3H">
|
||||
<rect key="frame" x="16" y="72" width="343" height="343"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="UCf-TJ-T3H" secondAttribute="height" multiplier="1:1" id="79z-Mc-kAW"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="1TI-Mv-rG8">
|
||||
<rect key="frame" x="16" y="423" width="343" height="93.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Currently Playing Item Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qr1-Cy-evY">
|
||||
<rect key="frame" x="68.5" y="0.0" width="206" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Currently Playing Item Album" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WDE-d7-320">
|
||||
<rect key="frame" x="60.5" y="36.5" width="222" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Currently Playing Item Artist" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="51g-Hm-Urz">
|
||||
<rect key="frame" x="64.5" y="73" width="214.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Sen-wE-t8H">
|
||||
<rect key="frame" x="16" y="566" width="343" height="44"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Xqo-UP-lVH">
|
||||
<rect key="frame" x="0.0" y="11" width="109" height="22"/>
|
||||
<state key="normal" image="Backward"/>
|
||||
<connections>
|
||||
<action selector="BtnSkipToPrevious_TouchUpInside:" destination="MSK-cM-e61" eventType="touchUpInside" id="316"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4LR-S1-yOe">
|
||||
<rect key="frame" x="117" y="9.5" width="109" height="25"/>
|
||||
<state key="normal" image="Play"/>
|
||||
<connections>
|
||||
<action selector="BtnPlayPause_TouchUpInside:" destination="MSK-cM-e61" eventType="touchUpInside" id="317"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="am3-2R-yDW">
|
||||
<rect key="frame" x="234" y="11" width="109" height="22"/>
|
||||
<state key="normal" image="Forward"/>
|
||||
<connections>
|
||||
<action selector="BtnSkipToNext_TouchUpInside:" destination="MSK-cM-e61" eventType="touchUpInside" id="318"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="c79-9t-2h0"/>
|
||||
</constraints>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="UCf-TJ-T3H" firstAttribute="leading" secondItem="LkG-6K-ggf" secondAttribute="leadingMargin" id="2am-bg-YO6"/>
|
||||
<constraint firstItem="Sen-wE-t8H" firstAttribute="leading" secondItem="LkG-6K-ggf" secondAttribute="leadingMargin" id="5vG-NL-sUQ"/>
|
||||
<constraint firstItem="UCf-TJ-T3H" firstAttribute="top" secondItem="NNN-oa-KF2" secondAttribute="bottom" constant="8" id="CqT-xb-lHz"/>
|
||||
<constraint firstItem="1TI-Mv-rG8" firstAttribute="leading" secondItem="LkG-6K-ggf" secondAttribute="leadingMargin" id="GXA-lp-W9B"/>
|
||||
<constraint firstItem="Sen-wE-t8H" firstAttribute="top" relation="greaterThanOrEqual" secondItem="1TI-Mv-rG8" secondAttribute="bottom" constant="8" id="H7w-Ub-y7c"/>
|
||||
<constraint firstItem="ELQ-Ug-Qpc" firstAttribute="top" secondItem="Sen-wE-t8H" secondAttribute="bottom" constant="8" id="Pm7-0z-QP9"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Sen-wE-t8H" secondAttribute="trailing" id="T0a-rj-2F6"/>
|
||||
<constraint firstItem="1TI-Mv-rG8" firstAttribute="top" secondItem="UCf-TJ-T3H" secondAttribute="bottom" constant="8" id="deI-xN-RcG"/>
|
||||
<constraint firstItem="UCf-TJ-T3H" firstAttribute="trailing" secondItem="LkG-6K-ggf" secondAttribute="trailingMargin" id="vZh-T2-2aB"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="1TI-Mv-rG8" secondAttribute="trailing" id="wiy-bB-CLu"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Player" id="fDG-R9-Rh8"/>
|
||||
<connections>
|
||||
<outlet property="BtnPlayPause" destination="4LR-S1-yOe" id="name-outlet-4LR-S1-yOe"/>
|
||||
<outlet property="BtnSkipToNext" destination="am3-2R-yDW" id="name-outlet-am3-2R-yDW"/>
|
||||
<outlet property="BtnSkipToPrevious" destination="Xqo-UP-lVH" id="name-outlet-Xqo-UP-lVH"/>
|
||||
<outlet property="ImgCurrentArtwork" destination="UCf-TJ-T3H" id="name-outlet-UCf-TJ-T3H"/>
|
||||
<outlet property="LblCurrentAlbum" destination="WDE-d7-320" id="name-outlet-WDE-d7-320"/>
|
||||
<outlet property="LblCurrentArtist" destination="51g-Hm-Urz" id="name-outlet-51g-Hm-Urz"/>
|
||||
<outlet property="LblCurrentTitle" destination="Qr1-Cy-evY" id="name-outlet-Qr1-Cy-evY"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="76F-rK-6UF" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="773.60000000000002" y="450.22489999999999"/>
|
||||
</scene>
|
||||
<!--Tab Bar Controller-->
|
||||
<scene sceneID="bFY-n3-AGL">
|
||||
<objects>
|
||||
<tabBarController id="G6S-L2-L7g" sceneMemberID="viewController">
|
||||
<tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="FOo-DS-9lp">
|
||||
<rect key="frame" x="0.0" y="551" width="600" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</tabBar>
|
||||
<connections>
|
||||
<segue destination="yLq-6L-wi1" kind="relationship" relationship="viewControllers" id="Qmc-50-vv4"/>
|
||||
<segue destination="imD-Rp-qz1" kind="relationship" relationship="viewControllers" id="lSU-5F-bRg"/>
|
||||
<segue destination="O5W-Za-J4D" kind="relationship" relationship="viewControllers" id="22F-QN-Fha"/>
|
||||
<segue destination="Pvz-dp-e0H" kind="relationship" relationship="viewControllers" id="kkm-4M-AmL"/>
|
||||
<segue destination="Ym3-C6-D63" kind="relationship" relationship="viewControllers" id="F0g-Ys-KDY"/>
|
||||
</connections>
|
||||
</tabBarController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="aNi-Gq-vCf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1062" y="452"/>
|
||||
</scene>
|
||||
<!--Authorize-->
|
||||
<scene sceneID="S3o-I2-SYY">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="yLq-6L-wi1" sceneMemberID="viewController">
|
||||
<tabBarItem key="tabBarItem" title="Authorize" image="Configure" id="pLw-hQ-BbL"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="t0z-Gy-lDI">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="4De-or-GT5" kind="relationship" relationship="rootViewController" id="WZN-Oy-ig5"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="xcV-ur-ghO" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-66" y="-967"/>
|
||||
</scene>
|
||||
<!--Playlist-->
|
||||
<scene sceneID="eUu-6K-7oE">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="imD-Rp-qz1" sceneMemberID="viewController">
|
||||
<tabBarItem key="tabBarItem" title="Playlist" image="Assets" id="mzQ-jI-HZg"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="QFH-vh-cUw">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="Qah-g7-icU" kind="relationship" relationship="rootViewController" id="Av9-LR-HCz"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="kjT-zH-nZY" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-66" y="-255"/>
|
||||
</scene>
|
||||
<!--Player-->
|
||||
<scene sceneID="gvQ-mZ-r1w">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="O5W-Za-J4D" sceneMemberID="viewController">
|
||||
<tabBarItem key="tabBarItem" title="Player" image="Play" id="ysg-mC-MIe"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="PwR-rh-NCa">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="MSK-cM-e61" kind="relationship" relationship="rootViewController" id="qTN-k2-uRJ"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="eMZ-z4-rK3" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-66" y="451"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="pij-qI-sbu">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="Pvz-dp-e0H" sceneMemberID="viewController">
|
||||
<tabBarItem key="tabBarItem" systemItem="recents" id="Jfu-EL-wpP"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="zJi-1h-O2Z">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="gRZ-kl-2Qi" kind="relationship" relationship="rootViewController" id="mAX-zl-VXy"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Q3i-X1-xh0" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-66" y="1156"/>
|
||||
</scene>
|
||||
<!--Authorize-->
|
||||
<scene sceneID="Xv8-NK-viH">
|
||||
<objects>
|
||||
<tableViewController id="4De-or-GT5" customClass="AuthorizationTableViewController" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="vsr-6H-Jjx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="AuthorizationCellIdentifier" textLabel="e83-No-JYg" style="IBUITableViewCellStyleDefault" id="3sn-mh-JZR">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="3sn-mh-JZR" id="ez8-NY-SlV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="e83-No-JYg">
|
||||
<rect key="frame" x="16" y="0.0" width="344" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="4De-or-GT5" id="gED-f1-Gfq"/>
|
||||
<outlet property="delegate" destination="4De-or-GT5" id="TXS-Ag-oU8"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Authorize" id="c9j-7Z-oyR">
|
||||
<barButtonItem key="rightBarButtonItem" enabled="NO" title="Request" id="0I1-ri-bZx">
|
||||
<connections>
|
||||
<action selector="BtnRequestAuthorization_Tapped:" destination="4De-or-GT5" id="314"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="wcC-RU-5CH" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="774" y="-967"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="gWc-JR-yII">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="Ym3-C6-D63" sceneMemberID="viewController">
|
||||
<tabBarItem key="tabBarItem" systemItem="search" id="wXy-Pi-Q4M"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="vWF-XB-Z6K">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="F1E-J5-kwo" kind="relationship" relationship="rootViewController" id="1IY-VX-9A7"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="3cJ-Uz-mVL" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-66" y="1863"/>
|
||||
</scene>
|
||||
<!--Media Search-->
|
||||
<scene sceneID="6bF-ez-C1X">
|
||||
<objects>
|
||||
<tableViewController id="F1E-J5-kwo" customClass="MediaSearchTableViewController" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="s8m-Ux-aBE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="MediaItemTableViewCell" rowHeight="126" id="5ed-Tv-mHb" customClass="MediaItemTableViewCell">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="126"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="5ed-Tv-mHb" id="mZb-dm-cHg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="125.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="tJX-w6-S1T">
|
||||
<rect key="frame" x="8" y="8" width="90" height="90"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="90" id="QtU-Ct-d7L"/>
|
||||
<constraint firstAttribute="height" constant="90" id="U3W-BP-50x"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="2FY-Ev-SpN">
|
||||
<rect key="frame" x="106" y="8" width="261" height="87"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ke2-e6-TlN">
|
||||
<rect key="frame" x="0.0" y="0.0" width="261" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4gs-53-w1A">
|
||||
<rect key="frame" x="0.0" y="28.5" width="261" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="hhj-bL-uzK">
|
||||
<rect key="frame" x="0.0" y="57" width="261" height="30"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3qq-Yt-Vf3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="126.5" height="30"/>
|
||||
<state key="normal" title="Add to Playlist"/>
|
||||
<connections>
|
||||
<action selector="BtnAddToPlaylist_TouchUpInside:" destination="5ed-Tv-mHb" eventType="touchUpInside" id="321"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GhG-au-QZN">
|
||||
<rect key="frame" x="134.5" y="0.0" width="126.5" height="30"/>
|
||||
<state key="normal" title="Play"/>
|
||||
<connections>
|
||||
<action selector="BtnPlayItem_TouchUpInside:" destination="5ed-Tv-mHb" eventType="touchUpInside" id="322"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="tJX-w6-S1T" firstAttribute="top" secondItem="mZb-dm-cHg" secondAttribute="topMargin" id="0c9-WG-JOk"/>
|
||||
<constraint firstItem="tJX-w6-S1T" firstAttribute="leading" secondItem="mZb-dm-cHg" secondAttribute="leadingMargin" id="7gW-MU-FHM"/>
|
||||
<constraint firstItem="2FY-Ev-SpN" firstAttribute="leading" secondItem="tJX-w6-S1T" secondAttribute="trailing" constant="8" id="XR7-CU-ehV"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="2FY-Ev-SpN" secondAttribute="trailing" id="YML-tj-YcC"/>
|
||||
<constraint firstItem="2FY-Ev-SpN" firstAttribute="top" secondItem="mZb-dm-cHg" secondAttribute="topMargin" id="Yjp-92-gFK"/>
|
||||
<constraint firstAttribute="bottomMargin" relation="greaterThanOrEqual" secondItem="2FY-Ev-SpN" secondAttribute="bottom" id="eLV-KS-F5t"/>
|
||||
<constraint firstItem="2FY-Ev-SpN" firstAttribute="leading" secondItem="tJX-w6-S1T" secondAttribute="trailing" constant="8" id="hzq-Ym-UCo"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="BtnAddToPlaylist" destination="3qq-Yt-Vf3" id="name-outlet-3qq-Yt-Vf3"/>
|
||||
<outlet property="BtnPlayItem" destination="GhG-au-QZN" id="name-outlet-GhG-au-QZN"/>
|
||||
<outlet property="ImgAssetCoverArt" destination="tJX-w6-S1T" id="name-outlet-tJX-w6-S1T"/>
|
||||
<outlet property="LblArtist" destination="4gs-53-w1A" id="name-outlet-4gs-53-w1A"/>
|
||||
<outlet property="LblTitle" destination="ke2-e6-TlN" id="name-outlet-ke2-e6-TlN"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="F1E-J5-kwo" id="y3U-A8-UFP"/>
|
||||
<outlet property="delegate" destination="F1E-J5-kwo" id="onP-sF-CnZ"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Media Search" id="5bQ-dS-2EQ"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="aXh-7I-qZN" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="774" y="1863"/>
|
||||
</scene>
|
||||
<!--Playlist-->
|
||||
<scene sceneID="Omp-Lz-sKV">
|
||||
<objects>
|
||||
<tableViewController id="Qah-g7-icU" customClass="PlaylistTableViewController" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="Igj-qH-JHB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="PlaylistTableViewCell" rowHeight="126" id="qSH-a3-LZx" customClass="PlaylistTableViewCell">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="126"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qSH-a3-LZx" id="ASb-Pc-ABE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="125.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Song" translatesAutoresizingMaskIntoConstraints="NO" id="HJp-ZA-wJp">
|
||||
<rect key="frame" x="16" y="16" width="90" height="90"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="90" id="l55-Wy-zM0"/>
|
||||
<constraint firstAttribute="width" constant="90" id="nc6-YJ-gSz"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="J36-Cz-KBP">
|
||||
<rect key="frame" x="114" y="16" width="245" height="85.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GWP-10-pbO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="245" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MMI-Ok-76o">
|
||||
<rect key="frame" x="0.0" y="32.5" width="245" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UiQ-Hp-EvZ">
|
||||
<rect key="frame" x="0.0" y="65" width="245" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="HJp-ZA-wJp" firstAttribute="top" secondItem="ASb-Pc-ABE" secondAttribute="topMargin" constant="8" id="9BA-KL-IFO"/>
|
||||
<constraint firstItem="J36-Cz-KBP" firstAttribute="leading" secondItem="HJp-ZA-wJp" secondAttribute="trailing" constant="8" symbolic="YES" id="ORr-St-UoD"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="J36-Cz-KBP" secondAttribute="bottom" constant="16" id="WnT-0g-CL6"/>
|
||||
<constraint firstItem="HJp-ZA-wJp" firstAttribute="leading" secondItem="ASb-Pc-ABE" secondAttribute="leadingMargin" constant="8" id="j2F-8E-NuH"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="J36-Cz-KBP" secondAttribute="trailing" constant="8" id="pb2-73-zVM"/>
|
||||
<constraint firstItem="J36-Cz-KBP" firstAttribute="top" secondItem="ASb-Pc-ABE" secondAttribute="topMargin" constant="8" id="rnq-a7-qqC"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="ImgAssetCoverArt" destination="HJp-ZA-wJp" id="name-outlet-HJp-ZA-wJp"/>
|
||||
<outlet property="LblAlbum" destination="MMI-Ok-76o" id="name-outlet-MMI-Ok-76o"/>
|
||||
<outlet property="LblArtist" destination="UiQ-Hp-EvZ" id="name-outlet-UiQ-Hp-EvZ"/>
|
||||
<outlet property="LblTitle" destination="GWP-10-pbO" id="name-outlet-GWP-10-pbO"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="Qah-g7-icU" id="gO6-0h-fR5"/>
|
||||
<outlet property="delegate" destination="Qah-g7-icU" id="H1g-Ez-lbu"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Playlist" id="u9q-cv-xF2">
|
||||
<barButtonItem key="rightBarButtonItem" title="Play" id="HKW-Za-JTV">
|
||||
<connections>
|
||||
<action selector="BtnPlayPlaylist_Tapped:" destination="Qah-g7-icU" id="315"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="NYc-a1-ZOH" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="774" y="-255"/>
|
||||
</scene>
|
||||
<!--Recents-->
|
||||
<scene sceneID="eDj-yF-Da7">
|
||||
<objects>
|
||||
<tableViewController id="gRZ-kl-2Qi" customClass="RecentlyPlayedTableViewController" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="aON-cu-al3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="MediaItemTableViewCell" rowHeight="126" id="jhH-aV-hh0" customClass="MediaItemTableViewCell">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="126"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="jhH-aV-hh0" id="4UB-kM-YfL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="125.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Album" translatesAutoresizingMaskIntoConstraints="NO" id="KHn-Gg-ei4">
|
||||
<rect key="frame" x="8" y="8" width="90" height="90"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="90" id="5Um-p4-Jgj"/>
|
||||
<constraint firstAttribute="width" constant="90" id="LEF-MA-2Tf"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Yw5-fF-a7U">
|
||||
<rect key="frame" x="106" y="8" width="261" height="87"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xWI-Jr-ejP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="261" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Kgn-NB-kB6">
|
||||
<rect key="frame" x="0.0" y="28.5" width="261" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="yGy-Gi-wEb">
|
||||
<rect key="frame" x="0.0" y="57" width="261" height="30"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="s3g-oh-Bwv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="126.5" height="30"/>
|
||||
<state key="normal" title="Add to Playlist"/>
|
||||
<connections>
|
||||
<action selector="BtnAddToPlaylist_TouchUpInside:" destination="jhH-aV-hh0" eventType="touchUpInside" id="319"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="t8v-p5-ddU">
|
||||
<rect key="frame" x="134.5" y="0.0" width="126.5" height="30"/>
|
||||
<state key="normal" title="Play"/>
|
||||
<connections>
|
||||
<action selector="BtnPlayItem_TouchUpInside:" destination="jhH-aV-hh0" eventType="touchUpInside" id="320"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Yw5-fF-a7U" firstAttribute="leading" secondItem="KHn-Gg-ei4" secondAttribute="trailing" constant="8" id="Uqi-21-uoA"/>
|
||||
<constraint firstAttribute="bottomMargin" relation="greaterThanOrEqual" secondItem="Yw5-fF-a7U" secondAttribute="bottom" id="X9c-QR-Pee"/>
|
||||
<constraint firstItem="KHn-Gg-ei4" firstAttribute="leading" secondItem="4UB-kM-YfL" secondAttribute="leading" constant="8" id="dJH-58-z8v"/>
|
||||
<constraint firstItem="KHn-Gg-ei4" firstAttribute="top" secondItem="4UB-kM-YfL" secondAttribute="top" constant="8" id="mAK-g0-LF1"/>
|
||||
<constraint firstItem="Yw5-fF-a7U" firstAttribute="top" secondItem="4UB-kM-YfL" secondAttribute="topMargin" id="wMe-Vz-4cS"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Yw5-fF-a7U" secondAttribute="trailing" id="wzZ-Vl-FJb"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="BtnAddToPlaylist" destination="s3g-oh-Bwv" id="name-outlet-s3g-oh-Bwv"/>
|
||||
<outlet property="BtnPlayItem" destination="t8v-p5-ddU" id="name-outlet-t8v-p5-ddU"/>
|
||||
<outlet property="ImgAssetCoverArt" destination="KHn-Gg-ei4" id="name-outlet-KHn-Gg-ei4"/>
|
||||
<outlet property="LblArtist" destination="Kgn-NB-kB6" id="name-outlet-Kgn-NB-kB6"/>
|
||||
<outlet property="LblTitle" destination="xWI-Jr-ejP" id="name-outlet-xWI-Jr-ejP"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="gRZ-kl-2Qi" id="xPv-MT-Zp2"/>
|
||||
<outlet property="delegate" destination="gRZ-kl-2Qi" id="JKF-RB-yr7"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Recents" id="I7l-vM-ar9"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="qID-3t-sqt" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="774" y="1155"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="Album" width="90" height="90"/>
|
||||
<image name="Assets" width="28" height="22"/>
|
||||
<image name="Backward" width="30" height="20"/>
|
||||
<image name="Configure" width="28" height="28"/>
|
||||
<image name="Forward" width="30" height="20"/>
|
||||
<image name="Play" width="25" height="25"/>
|
||||
<image name="Song" width="90" height="90"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
Abstract:
|
||||
`Artwork` represents a `Artwork` object from the Apple Music Web Services.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Foundation;
|
||||
using System.Collections.Generic;
|
||||
using CoreGraphics;
|
||||
namespace MusicKitSample.Models
|
||||
{
|
||||
public class Artwork
|
||||
{
|
||||
#region Types
|
||||
|
||||
// The various keys needed for serializing an instance of `Artwork` using a JSON response from the Apple Music Web Service.
|
||||
struct JsonKeys
|
||||
{
|
||||
public static readonly string Height = "height";
|
||||
public static readonly string Width = "width";
|
||||
public static readonly string Url = "url";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// The maximum height available for the image.
|
||||
public int Height { get; set; }
|
||||
/// The maximum width available for the image.
|
||||
public int Width { get; set; }
|
||||
|
||||
/*
|
||||
* The string representation of the URL to request the image asset. This template should be used to create the URL for the correctly sized image
|
||||
* your application wishes to use. See `Artwork.imageURL(size:)` for additional information.
|
||||
*/
|
||||
public string UrlTemplate { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
Artwork ()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Constructors
|
||||
|
||||
public static Artwork Create () => From (0, 0, string.Empty);
|
||||
|
||||
public static Artwork From (int height, int width, string urlTemplate)
|
||||
{
|
||||
return new Artwork {
|
||||
Height = height,
|
||||
Width = width,
|
||||
UrlTemplate = urlTemplate
|
||||
};
|
||||
}
|
||||
|
||||
public static Artwork From (NSDictionary json)
|
||||
{
|
||||
var heightString = json [JsonKeys.Height]?.ToString () ?? throw new SerializationException (JsonKeys.Height);
|
||||
var widthString = json [JsonKeys.Width]?.ToString () ?? throw new SerializationException (JsonKeys.Width);
|
||||
var urlTemplate = json [JsonKeys.Url]?.ToString () ?? throw new SerializationException (JsonKeys.Url);
|
||||
|
||||
int.TryParse (json [JsonKeys.Height]?.ToString () ?? string.Empty, out int height);
|
||||
int.TryParse (json [JsonKeys.Width]?.ToString () ?? string.Empty, out int width);
|
||||
|
||||
return From (height, width, urlTemplate);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functionality
|
||||
|
||||
public NSUrl GenerateImageUrl () => GenerateImageUrl (new CGSize (Width, Height));
|
||||
|
||||
public NSUrl GenerateImageUrl (CGSize size)
|
||||
{
|
||||
/*
|
||||
* There are three pieces of information needed to create
|
||||
* the Url for the image we want for a given size.
|
||||
* This information is the width, height and image format.
|
||||
* We can use this information in addition to the `urlTemplate`
|
||||
* to create the Url for the image we wish to use.
|
||||
*/
|
||||
|
||||
// 1) Replace the "{w}" placeholder with the desired width as an integer value.
|
||||
var imageUrl = UrlTemplate.Replace ("{w}", size.Width.ToString ());
|
||||
|
||||
// 2) Replace the "{h}" placeholder with the desired height as an integer value.
|
||||
imageUrl = imageUrl.Replace ("{h}", size.Height.ToString ());
|
||||
|
||||
// 3) Replace the "{f}" placeholder with the desired image format.
|
||||
imageUrl = imageUrl.Replace ("{f}", "png");
|
||||
|
||||
return NSUrl.FromString (imageUrl);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
Abstract:
|
||||
`MediaItem` represents a `Resource` object from the Apple Music Web Services.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Foundation;
|
||||
namespace MusicKitSample.Models
|
||||
{
|
||||
public class MediaItem
|
||||
{
|
||||
#region Types
|
||||
|
||||
// The various keys needed for serializing an instance of `MediaItem`
|
||||
// using a Json response from the Apple Music Web Service.
|
||||
struct JsonKeys
|
||||
{
|
||||
public static readonly string Id = "id";
|
||||
public static readonly string Type = "type";
|
||||
public static readonly string Attributes = "attributes";
|
||||
public static readonly string Name = "name";
|
||||
public static readonly string ArtistName = "artistName";
|
||||
public static readonly string Artwork = "artwork";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
// The persistent identifier of the resource which is used
|
||||
// to add the item to the playlist or trigger playback.
|
||||
public string Id { get; set; }
|
||||
|
||||
// The localized name of the album or song.
|
||||
public string Name { get; set; }
|
||||
|
||||
// The artist’s name.
|
||||
public string ArtistName { get; set; }
|
||||
|
||||
// The album artwork associated with the song or album.
|
||||
public Artwork Artwork { get; set; }
|
||||
|
||||
// The type of the `MediaItem` which in this application can be either `songs` or `albums`.
|
||||
public MediaType Type { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
MediaItem ()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Functionality
|
||||
|
||||
public static MediaItem Create () => From (string.Empty, string.Empty, string.Empty, Artwork.Create (), MediaType.Songs);
|
||||
|
||||
public static MediaItem From (string id, string name, string artistName, Artwork artwork, MediaType type)
|
||||
{
|
||||
return new MediaItem {
|
||||
Id = id,
|
||||
Name = name,
|
||||
ArtistName = artistName,
|
||||
Artwork = artwork,
|
||||
Type = type
|
||||
};
|
||||
}
|
||||
|
||||
public static MediaItem From (NSDictionary json)
|
||||
{
|
||||
var id = json [JsonKeys.Id]?.ToString () ?? throw new SerializationException (JsonKeys.Id);
|
||||
|
||||
var typeString = json [JsonKeys.Type]?.ToString () ?? throw new SerializationException (JsonKeys.Type);
|
||||
var type = (MediaType)Enum.Parse (typeof (MediaType), typeString, true);
|
||||
|
||||
var attributes = json [JsonKeys.Attributes] as NSDictionary ??
|
||||
throw new SerializationException (JsonKeys.Attributes);
|
||||
var name = attributes [JsonKeys.Name]?.ToString () ?? throw new SerializationException (JsonKeys.Name);
|
||||
|
||||
var artworkJson = attributes [JsonKeys.Artwork] as NSDictionary ??
|
||||
throw new SerializationException (JsonKeys.Artwork);
|
||||
var artwork = Artwork.From (artworkJson);
|
||||
|
||||
var artistName = attributes [JsonKeys.ArtistName]?.ToString () ?? string.Empty;
|
||||
|
||||
return From (id, name, artistName, artwork, type);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
namespace MusicKitSample.Models
|
||||
{
|
||||
// - Songs: This indicates that the `MediaItem` is a song from the Apple Music Catalog.
|
||||
// - Albums: This indicates that the `MediaItem` is an album from the Apple Music Catalog.
|
||||
public enum MediaType
|
||||
{
|
||||
Songs,
|
||||
Albums,
|
||||
Stations,
|
||||
Playlists,
|
||||
Unknown
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
namespace MusicKitSample.Models
|
||||
{
|
||||
public class SerializationException : Exception
|
||||
{
|
||||
#region Properties
|
||||
|
||||
public string JsonKey { get; private set; }
|
||||
|
||||
public override string Message {
|
||||
get {
|
||||
string message = base.Message;
|
||||
|
||||
if (!string.IsNullOrEmpty (JsonKey))
|
||||
return message + Environment.NewLine + $"The key {JsonKey} is missing.";
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public SerializationException () : this (string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public SerializationException (string jsonKey)
|
||||
{
|
||||
JsonKey = jsonKey;
|
||||
}
|
||||
|
||||
public SerializationException (string jsonKey, string message) : base (message)
|
||||
{
|
||||
JsonKey = jsonKey;
|
||||
}
|
||||
|
||||
public SerializationException (string jsonKey, string message, Exception inner) : base (message, inner)
|
||||
{
|
||||
JsonKey = jsonKey;
|
||||
}
|
||||
|
||||
public SerializationException (SerializationInfo info, StreamingContext context) : base (info, context)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform>
|
||||
<ProjectGuid>{BCBBC179-9DD9-4630-B712-FFDB3BFBCAEF}</ProjectGuid>
|
||||
<ProjectTypeGuids>{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>MusicKitSample</RootNamespace>
|
||||
<AssemblyName>MusicKitSample</AssemblyName>
|
||||
<IPhoneResourcePrefix>Resources</IPhoneResourcePrefix>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\iPhoneSimulator\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;ENABLE_TEST_CLOUD;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
<MtouchDebug>true</MtouchDebug>
|
||||
<MtouchNoSymbolStrip>true</MtouchNoSymbolStrip>
|
||||
<MtouchFastDev>true</MtouchFastDev>
|
||||
<MtouchProfiling>true</MtouchProfiling>
|
||||
<IOSDebuggerPort>23990</IOSDebuggerPort>
|
||||
<MtouchLink>None</MtouchLink>
|
||||
<MtouchArch>i386, x86_64</MtouchArch>
|
||||
<MtouchHttpClientHandler>HttpClientHandler</MtouchHttpClientHandler>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\iPhone\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
<MtouchFloat32>true</MtouchFloat32>
|
||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||
<MtouchLink>SdkOnly</MtouchLink>
|
||||
<MtouchArch>ARMv7, ARM64</MtouchArch>
|
||||
<MtouchHttpClientHandler>HttpClientHandler</MtouchHttpClientHandler>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\iPhoneSimulator\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
<MtouchNoSymbolStrip>true</MtouchNoSymbolStrip>
|
||||
<MtouchLink>None</MtouchLink>
|
||||
<MtouchArch>i386, x86_64</MtouchArch>
|
||||
<MtouchHttpClientHandler>HttpClientHandler</MtouchHttpClientHandler>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\iPhone\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;ENABLE_TEST_CLOUD;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodesignKey>iPhone Developer</CodesignKey>
|
||||
<DeviceSpecificBuild>true</DeviceSpecificBuild>
|
||||
<MtouchDebug>true</MtouchDebug>
|
||||
<MtouchNoSymbolStrip>true</MtouchNoSymbolStrip>
|
||||
<MtouchFastDev>true</MtouchFastDev>
|
||||
<MtouchProfiling>true</MtouchProfiling>
|
||||
<MtouchFloat32>true</MtouchFloat32>
|
||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||
<IOSDebuggerPort>60707</IOSDebuggerPort>
|
||||
<MtouchLink>SdkOnly</MtouchLink>
|
||||
<MtouchArch>ARMv7, ARM64</MtouchArch>
|
||||
<MtouchHttpClientHandler>HttpClientHandler</MtouchHttpClientHandler>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Xamarin.iOS" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\Contents.json" />
|
||||
<ImageAsset Include="Assets.xcassets\Contents.json" />
|
||||
<ImageAsset Include="Assets.xcassets\Assets.imageset\Assets.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Assets.imageset\Assets%402x.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Assets.imageset\Assets%403x.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Assets.imageset\Contents.json" />
|
||||
<ImageAsset Include="Assets.xcassets\Backward.imageset\Backward.pdf" />
|
||||
<ImageAsset Include="Assets.xcassets\Backward.imageset\Contents.json" />
|
||||
<ImageAsset Include="Assets.xcassets\Configure.imageset\Configure.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Configure.imageset\Configure%402x.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Configure.imageset\Configure%403x.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Configure.imageset\Contents.json" />
|
||||
<ImageAsset Include="Assets.xcassets\Forward.imageset\Contents.json" />
|
||||
<ImageAsset Include="Assets.xcassets\Forward.imageset\Forward.pdf" />
|
||||
<ImageAsset Include="Assets.xcassets\Pause.imageset\Contents.json" />
|
||||
<ImageAsset Include="Assets.xcassets\Pause.imageset\Pause.pdf" />
|
||||
<ImageAsset Include="Assets.xcassets\Play.imageset\Contents.json" />
|
||||
<ImageAsset Include="Assets.xcassets\Play.imageset\Play.pdf" />
|
||||
<ImageAsset Include="Assets.xcassets\Song.imageset\Contents.json" />
|
||||
<ImageAsset Include="Assets.xcassets\Album.imageset\Contents.json" />
|
||||
<ImageAsset Include="Assets.xcassets\Song.imageset\song_thumbnail.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Song.imageset\song_thumbnail%402x.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Song.imageset\song_thumbnail%403x.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Album.imageset\album_thumbnail.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Album.imageset\album_thumbnail%402x.png" />
|
||||
<ImageAsset Include="Assets.xcassets\Album.imageset\album_thumbnail%403x.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Controllers\" />
|
||||
<Folder Include="Views\" />
|
||||
<Folder Include="Models\" />
|
||||
<Folder Include="Utilities\" />
|
||||
<Folder Include="Assets.xcassets\Song.imageset\" />
|
||||
<Folder Include="Assets.xcassets\Album.imageset\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<InterfaceDefinition Include="LaunchScreen.storyboard" />
|
||||
<InterfaceDefinition Include="Main.storyboard" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Info.plist" />
|
||||
<None Include="Entitlements.plist" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="AppDelegate.cs" />
|
||||
<Compile Include="Views\PlayerViewController.cs" />
|
||||
<Compile Include="Views\PlayerViewController.designer.cs">
|
||||
<DependentUpon>PlayerViewController.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\RecentlyPlayedTableViewController.cs" />
|
||||
<Compile Include="Views\RecentlyPlayedTableViewController.designer.cs">
|
||||
<DependentUpon>RecentlyPlayedTableViewController.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\AuthorizationTableViewController.cs" />
|
||||
<Compile Include="Views\AuthorizationTableViewController.designer.cs">
|
||||
<DependentUpon>AuthorizationTableViewController.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\MediaSearchTableViewController.cs" />
|
||||
<Compile Include="Views\MediaSearchTableViewController.designer.cs">
|
||||
<DependentUpon>MediaSearchTableViewController.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\PlaylistTableViewController.cs" />
|
||||
<Compile Include="Views\PlaylistTableViewController.designer.cs">
|
||||
<DependentUpon>PlaylistTableViewController.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\PlaylistTableViewCell.cs" />
|
||||
<Compile Include="Views\PlaylistTableViewCell.designer.cs">
|
||||
<DependentUpon>PlaylistTableViewCell.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\MediaItemTableViewCell.cs" />
|
||||
<Compile Include="Views\MediaItemTableViewCell.designer.cs">
|
||||
<DependentUpon>MediaItemTableViewCell.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Models\Artwork.cs" />
|
||||
<Compile Include="Models\MediaItem.cs" />
|
||||
<Compile Include="Models\MediaType.cs" />
|
||||
<Compile Include="Utilities\JsonKeys.cs" />
|
||||
<Compile Include="Utilities\AppleMusicRequestFactory.cs" />
|
||||
<Compile Include="Controllers\AppleMusicManager.cs" />
|
||||
<Compile Include="Models\SerializationException.cs" />
|
||||
<Compile Include="Controllers\AuthorizationManager.cs" />
|
||||
<Compile Include="Controllers\AuthorizationDataSource.cs" />
|
||||
<Compile Include="Controllers\ImageCacheManager.cs" />
|
||||
<Compile Include="Controllers\MediaLibraryManager.cs" />
|
||||
<Compile Include="Controllers\MusicPlayerManager.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
Abstract:
|
||||
The `AppleMusicRequestFactory` type is used to build the various Apple Music API calls used by the sample.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Foundation;
|
||||
using System.Collections.Generic;
|
||||
namespace MusicKitSample.Utilities
|
||||
{
|
||||
public struct AppleMusicRequestFactory
|
||||
{
|
||||
#region Types
|
||||
|
||||
// The base URL for all Apple Music API network calls.
|
||||
static readonly string appleMusicApiBaseUrl = "api.music.apple.com";
|
||||
|
||||
// The Apple Music API endpoint for requesting a list of recently played items.
|
||||
static readonly string recentlyPlayedPathUrl = "/v1/me/recent/played";
|
||||
|
||||
// The Apple Music API endpoint for requesting a the storefront of the currently logged in iTunes Store account.
|
||||
static readonly string userStorefrontPathUrl = "/v1/me/storefront";
|
||||
|
||||
public static NSUrlRequest CreateSearchRequest (string term, string countryCode, string developerToken)
|
||||
{
|
||||
// Create the URL components for the network call.
|
||||
var urlComponents = CreateUrlComponents ($"/v1/catalog/{countryCode}/search");
|
||||
|
||||
var expectedTerms = term.Replace (" ", "+");
|
||||
var urlParameters = new Dictionary<string, string> {
|
||||
{ "term", expectedTerms },
|
||||
{ "limit", "10" },
|
||||
{ "types", "songs,albums" }
|
||||
};
|
||||
|
||||
var queryItems = new List<NSUrlQueryItem> ();
|
||||
foreach (var urlParameter in urlParameters)
|
||||
queryItems.Add (new NSUrlQueryItem (urlParameter.Key, urlParameter.Value));
|
||||
|
||||
urlComponents.QueryItems = queryItems.ToArray ();
|
||||
|
||||
// Create and configure the `URLRequest`.
|
||||
var urlRequest = new NSMutableUrlRequest (urlComponents.Url) { HttpMethod = "GET" };
|
||||
urlRequest.Headers = new NSDictionary ("Authorization", $"Bearer {developerToken}");
|
||||
|
||||
return urlRequest;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functionality
|
||||
|
||||
public static NSUrlRequest CreateStorefrontsRequest (string regionCode, string developerToken)
|
||||
{
|
||||
// Create the URL components for the network call.
|
||||
var urlComponents = CreateUrlComponents ($"/v1/storefronts/{regionCode}");
|
||||
|
||||
// Create and configure the `URLRequest`.
|
||||
var urlRequest = new NSMutableUrlRequest (urlComponents.Url) { HttpMethod = "GET" };
|
||||
urlRequest.Headers = new NSDictionary ("Authorization", $"Bearer {developerToken}");
|
||||
|
||||
return urlRequest;
|
||||
}
|
||||
|
||||
public static NSUrlRequest CreateRecentlyPlayedRequest (string developerToken, string userToken)
|
||||
{
|
||||
// Create the URL components for the network call.
|
||||
var urlComponents = CreateUrlComponents (recentlyPlayedPathUrl);
|
||||
|
||||
// Create and configure the `URLRequest`.
|
||||
var urlRequest = new NSMutableUrlRequest (urlComponents.Url) { HttpMethod = "GET" };
|
||||
urlRequest.Headers = new NSDictionary ("Authorization", $"Bearer {developerToken}",
|
||||
"Music-User-Token", userToken);
|
||||
|
||||
return urlRequest;
|
||||
}
|
||||
|
||||
public static NSUrlRequest CreateGetUserStorefrontRequest (string developerToken, string userToken)
|
||||
{
|
||||
// Create the URL components for the network call.
|
||||
var urlComponents = CreateUrlComponents (userStorefrontPathUrl);
|
||||
|
||||
// Create and configure the `URLRequest`.
|
||||
var urlRequest = new NSMutableUrlRequest (urlComponents.Url) { HttpMethod = "GET" };
|
||||
urlRequest.Headers = new NSDictionary ("Authorization", $"Bearer {developerToken}",
|
||||
"Music-User-Token", userToken);
|
||||
|
||||
return urlRequest;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functionality
|
||||
|
||||
static NSUrlComponents CreateUrlComponents (string fromPath)
|
||||
{
|
||||
return new NSUrlComponents {
|
||||
Scheme = "https",
|
||||
Host = appleMusicApiBaseUrl,
|
||||
Path = fromPath
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Abstract:
|
||||
Various JSON keys needed when making calls to the Apple Music API.
|
||||
*/
|
||||
|
||||
using System;
|
||||
namespace MusicKitSample.Utilities
|
||||
{
|
||||
// Keys related to the `Response Root` JSON object in the Apple Music API.
|
||||
public struct ResponseRootJsonKeys
|
||||
{
|
||||
public static readonly string Data = "data";
|
||||
public static readonly string Results = "results";
|
||||
}
|
||||
|
||||
// Keys related to the `Resource` JSON object in the Apple Music API.
|
||||
public struct ResourceJsonKeys
|
||||
{
|
||||
public static readonly string Id = "id";
|
||||
public static readonly string Attributes = "attributes";
|
||||
public static readonly string Type = "type";
|
||||
}
|
||||
|
||||
// The various keys needed for parsing a JSON response from the Apple Music Web Service.
|
||||
public struct ResourceTypeJsonKeys
|
||||
{
|
||||
public static readonly string Songs = "songs";
|
||||
public static readonly string Albums = "albums";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
// This file has been autogenerated from a class added in the UI designer.
|
||||
|
||||
using System;
|
||||
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
using MusicKitSample.Controllers;
|
||||
using StoreKit;
|
||||
using MediaPlayer;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
public partial class AuthorizationTableViewController : UITableViewController, ISKCloudServiceSetupViewControllerDelegate
|
||||
{
|
||||
#region Fields
|
||||
|
||||
NSObject cloudServiceDidUpdateNotificationToken;
|
||||
NSObject authorizationDidUpdateNotificationToken;
|
||||
NSObject willEnterForegroundNotificationToken;
|
||||
|
||||
// The instance of `AuthorizationDataSource` that provides
|
||||
// information for the `UITableView`.
|
||||
AuthorizationDataSource authorizationDataSource;
|
||||
|
||||
// A boolean value representing if a
|
||||
// `SKCloudServiceSetupViewController` was presented while the
|
||||
// application was running.
|
||||
bool didPresentCloudServiceSetup;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
// The instance of `AuthorizationManager` used for querying and
|
||||
// requesting authorization status.
|
||||
public AuthorizationManager AuthorizationManager { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
protected AuthorizationTableViewController (IntPtr handle) : base (handle)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Controller Life Cycle
|
||||
|
||||
public override void ViewDidLoad ()
|
||||
{
|
||||
base.ViewDidLoad ();
|
||||
|
||||
authorizationDataSource = new AuthorizationDataSource (AuthorizationManager);
|
||||
|
||||
// Add the notification observers needed to respond to events
|
||||
// from the `AuthorizationManager` and `UIApplication`.
|
||||
var notificationCenter = NSNotificationCenter.DefaultCenter;
|
||||
cloudServiceDidUpdateNotificationToken = notificationCenter.AddObserver (AuthorizationManager.CloudServiceDidUpdateNotification,
|
||||
HandleAuthorizationManagerDidUpdateNotification,
|
||||
null);
|
||||
authorizationDidUpdateNotificationToken = notificationCenter.AddObserver (AuthorizationManager.AuthorizationDidUpdateNotification,
|
||||
HandleAuthorizationManagerDidUpdateNotification,
|
||||
null);
|
||||
willEnterForegroundNotificationToken = notificationCenter.AddObserver (UIApplication.WillEnterForegroundNotification,
|
||||
HandleAuthorizationManagerDidUpdateNotification,
|
||||
null);
|
||||
|
||||
SetAuthorizationRequestButtonState ();
|
||||
}
|
||||
|
||||
public override void ViewWillAppear (bool animated)
|
||||
{
|
||||
base.ViewWillAppear (animated);
|
||||
|
||||
SetAuthorizationRequestButtonState ();
|
||||
}
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
// Remove all notification observers.
|
||||
var notificationCenter = NSNotificationCenter.DefaultCenter;
|
||||
notificationCenter.RemoveObserver (cloudServiceDidUpdateNotificationToken);
|
||||
notificationCenter.RemoveObserver (authorizationDidUpdateNotificationToken);
|
||||
notificationCenter.RemoveObserver (willEnterForegroundNotificationToken);
|
||||
|
||||
cloudServiceDidUpdateNotificationToken = authorizationDidUpdateNotificationToken = willEnterForegroundNotificationToken = null;
|
||||
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI Updating Methods
|
||||
|
||||
void SetAuthorizationRequestButtonState ()
|
||||
{
|
||||
if (SKCloudServiceController.AuthorizationStatus == SKCloudServiceAuthorizationStatus.NotDetermined ||
|
||||
MPMediaLibrary.AuthorizationStatus == MPMediaLibraryAuthorizationStatus.NotDetermined)
|
||||
NavigationItem.RightBarButtonItem.Enabled = true;
|
||||
else
|
||||
NavigationItem.RightBarButtonItem.Enabled = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region User Interactions
|
||||
|
||||
partial void BtnRequestAuthorization_Tapped (UIBarButtonItem sender)
|
||||
{
|
||||
Task.Factory.StartNew (async () => {
|
||||
await AuthorizationManager.RequestCloudServiceAuthorizationAsync ();
|
||||
await AuthorizationManager.RequestMediaLibraryAuthorizationAsync ();
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Table View Data Source
|
||||
|
||||
public override nint NumberOfSections (UITableView tableView) => authorizationDataSource.NumberOfSections ();
|
||||
|
||||
public override nint RowsInSection (UITableView tableView, nint section) => authorizationDataSource.NumberOfItems (section);
|
||||
|
||||
public override string TitleForHeader (UITableView tableView, nint section) => authorizationDataSource.SectionTitle (section);
|
||||
|
||||
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
var cell = tableView.DequeueReusableCell ("AuthorizationCellIdentifier", indexPath);
|
||||
cell.TextLabel.Text = authorizationDataSource.StringForItem (indexPath);
|
||||
return cell;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SKCloudServiceSetupViewController Method
|
||||
|
||||
async Task PresentCloudServiceSetupAsync ()
|
||||
{
|
||||
if (didPresentCloudServiceSetup)
|
||||
return;
|
||||
|
||||
/*
|
||||
* If the current `SKCloudServiceCapability` includes
|
||||
* `.musicCatalogSubscriptionEligible`, this means that the currently signed
|
||||
* in iTunes Store account is elgible for an Apple Music Trial Subscription.
|
||||
* To provide the user with an option to sign up for a free trial, your
|
||||
* application can present the `SKCloudServiceSetupViewController` as
|
||||
* demonstrated below.
|
||||
*/
|
||||
var cloudServiceSetupViewController = new SKCloudServiceSetupViewController {
|
||||
Delegate = this
|
||||
};
|
||||
|
||||
var options = new SKCloudServiceSetupOptions ();
|
||||
options.Action = SKCloudServiceSetupAction.Subscribe;
|
||||
var result = await cloudServiceSetupViewController.LoadAsync (options);
|
||||
|
||||
if (result.Item2 != null)
|
||||
throw new NSErrorException (result.Item2);
|
||||
|
||||
if (result.Item1) {
|
||||
didPresentCloudServiceSetup = true;
|
||||
InvokeOnMainThread (() => PresentViewController (cloudServiceSetupViewController, true, null));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notification Observing Methods
|
||||
|
||||
void HandleAuthorizationManagerDidUpdateNotification (NSNotification notification)
|
||||
{
|
||||
if (SKCloudServiceController.AuthorizationStatus == SKCloudServiceAuthorizationStatus.NotDetermined ||
|
||||
MPMediaLibrary.AuthorizationStatus == MPMediaLibraryAuthorizationStatus.NotDetermined) {
|
||||
NavigationItem.RightBarButtonItem.Enabled = true;
|
||||
} else {
|
||||
NavigationItem.RightBarButtonItem.Enabled = false;
|
||||
|
||||
if (AuthorizationManager.CloudServiceCapabilities.HasFlag (SKCloudServiceCapability.MusicCatalogSubscriptionEligible) &&
|
||||
AuthorizationManager.CloudServiceCapabilities.HasFlag (SKCloudServiceCapability.MusicCatalogPlayback)) {
|
||||
PresentCloudServiceSetupAsync ();
|
||||
}
|
||||
}
|
||||
|
||||
TableView.ReloadData ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SKCloudServiceSetupViewController Delegate
|
||||
|
||||
[Export ("cloudServiceSetupViewControllerDidDismiss:")]
|
||||
public void DidDismiss (SKCloudServiceSetupViewController cloudServiceSetupViewController)
|
||||
{
|
||||
TableView.ReloadData ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
24
ios11/MusicKitSample/MusicKitSample/Views/AuthorizationTableViewController.designer.cs
сгенерированный
Normal file
|
@ -0,0 +1,24 @@
|
|||
// WARNING
|
||||
//
|
||||
// This file has been generated automatically by Visual Studio from the outlets and
|
||||
// actions declared in your storyboard file.
|
||||
// Manual changes to this file will not be maintained.
|
||||
//
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
[Register ("AuthorizationTableViewController")]
|
||||
partial class AuthorizationTableViewController
|
||||
{
|
||||
[Action ("BtnRequestAuthorization_Tapped:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void BtnRequestAuthorization_Tapped (UIKit.UIBarButtonItem sender);
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using MusicKitSample.Models;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
public partial class MediaItemTableViewCell : UITableViewCell
|
||||
{
|
||||
#region Cell Identifier
|
||||
|
||||
public static readonly NSString Key = new NSString ("MediaItemTableViewCell");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public UIImage AssetCoverArt {
|
||||
get { return ImgAssetCoverArt.Image; }
|
||||
set { ImgAssetCoverArt.Image = value; }
|
||||
}
|
||||
|
||||
public string Title {
|
||||
get { return LblTitle.Text; }
|
||||
set { LblTitle.Text = value; }
|
||||
}
|
||||
|
||||
public string Artist {
|
||||
get { return LblArtist.Text; }
|
||||
set { LblArtist.Text = value; }
|
||||
}
|
||||
|
||||
public bool AddToPlaylistButtonEnabled {
|
||||
get { return BtnAddToPlaylist.Enabled; }
|
||||
set { BtnAddToPlaylist.Enabled = value; }
|
||||
}
|
||||
|
||||
public bool PlayItemButtonEnabled {
|
||||
get { return BtnPlayItem.Enabled; }
|
||||
set { BtnPlayItem.Enabled = value; }
|
||||
}
|
||||
|
||||
MediaItem mediaItem;
|
||||
public MediaItem MediaItem {
|
||||
get { return mediaItem; }
|
||||
set {
|
||||
mediaItem = value;
|
||||
Title = value?.Name ?? string.Empty;
|
||||
Artist = value?.ArtistName ?? string.Empty;
|
||||
AssetCoverArt = null;
|
||||
}
|
||||
}
|
||||
|
||||
public IMediaSearchTableViewCellDelegate Delegate { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
protected MediaItemTableViewCell (IntPtr handle) : base (handle)
|
||||
{
|
||||
// Note: this .ctor should not contain any initialization logic.
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region User Interactions
|
||||
|
||||
partial void BtnAddToPlaylist_TouchUpInside (UIButton sender)
|
||||
{
|
||||
if (mediaItem != null)
|
||||
Delegate?.AddToPlaylist (this, mediaItem);
|
||||
}
|
||||
|
||||
partial void BtnPlayItem_TouchUpInside (UIButton sender)
|
||||
{
|
||||
if (mediaItem != null)
|
||||
Delegate?.PlayMediaItem (this, mediaItem);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public interface IMediaSearchTableViewCellDelegate
|
||||
{
|
||||
void AddToPlaylist (MediaItemTableViewCell mediaSearchTableViewCell, MediaItem mediaItem);
|
||||
void PlayMediaItem (MediaItemTableViewCell mediaSearchTableViewCell, MediaItem mediaItem);
|
||||
}
|
||||
}
|
72
ios11/MusicKitSample/MusicKitSample/Views/MediaItemTableViewCell.designer.cs
сгенерированный
Normal file
|
@ -0,0 +1,72 @@
|
|||
// WARNING
|
||||
//
|
||||
// This file has been generated automatically by Visual Studio from the outlets and
|
||||
// actions declared in your storyboard file.
|
||||
// Manual changes to this file will not be maintained.
|
||||
//
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
[Register ("MediaItemTableViewCell")]
|
||||
partial class MediaItemTableViewCell
|
||||
{
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIButton BtnAddToPlaylist { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIButton BtnPlayItem { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIImageView ImgAssetCoverArt { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UILabel LblArtist { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UILabel LblTitle { get; set; }
|
||||
|
||||
[Action ("BtnAddToPlaylist_TouchUpInside:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void BtnAddToPlaylist_TouchUpInside (UIKit.UIButton sender);
|
||||
|
||||
[Action ("BtnPlayItem_TouchUpInside:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void BtnPlayItem_TouchUpInside (UIKit.UIButton sender);
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
if (BtnAddToPlaylist != null) {
|
||||
BtnAddToPlaylist.Dispose ();
|
||||
BtnAddToPlaylist = null;
|
||||
}
|
||||
|
||||
if (BtnPlayItem != null) {
|
||||
BtnPlayItem.Dispose ();
|
||||
BtnPlayItem = null;
|
||||
}
|
||||
|
||||
if (ImgAssetCoverArt != null) {
|
||||
ImgAssetCoverArt.Dispose ();
|
||||
ImgAssetCoverArt = null;
|
||||
}
|
||||
|
||||
if (LblArtist != null) {
|
||||
LblArtist.Dispose ();
|
||||
LblArtist = null;
|
||||
}
|
||||
|
||||
if (LblTitle != null) {
|
||||
LblTitle.Dispose ();
|
||||
LblTitle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
// This file has been autogenerated from a class added in the UI designer.
|
||||
|
||||
using System;
|
||||
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
using MusicKitSample.Controllers;
|
||||
using MusicKitSample.Models;
|
||||
using System.Threading.Tasks;
|
||||
using CoreGraphics;
|
||||
using StoreKit;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
public partial class MediaSearchTableViewController : UITableViewController, IUISearchResultsUpdating, IUISearchBarDelegate, IMediaSearchTableViewCellDelegate
|
||||
{
|
||||
#region Fields
|
||||
|
||||
static readonly object padlock = new object ();
|
||||
|
||||
// The instance of `UISearchController` used for providing the
|
||||
// search funcationality in the `UITableView`.
|
||||
UISearchController searchController;
|
||||
|
||||
// The instance of `AppleMusicManager` which is used to make
|
||||
// search request calls to the Apple Music Web Services.
|
||||
AppleMusicManager appleMusicManager;
|
||||
|
||||
// The instance of `ImageCacheManager` that is used for
|
||||
// downloading and caching album artwork images.
|
||||
ImageCacheManager imageCacheManager;
|
||||
|
||||
MediaItem [] [] mediaItems;
|
||||
|
||||
NSObject authorizationDidUpdateNotificationToken;
|
||||
NSObject willEnterForegroundNotificationToken;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
// The instance of `AuthorizationManager` used for querying and
|
||||
// requesting authorization status.
|
||||
public AuthorizationManager AuthorizationManager { get; set; }
|
||||
|
||||
// The instance of `MusicPlayerManager` which is used for
|
||||
// triggering the playback of a `MediaItem`.
|
||||
public MusicPlayerManager MusicPlayerManager { get; set; }
|
||||
|
||||
// The instance of `MediaLibraryManager` which is used for
|
||||
// adding items to the application's playlist.
|
||||
public MediaLibraryManager MediaLibraryManager { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public MediaSearchTableViewController (IntPtr handle) : base (handle)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Controller Life Cycle
|
||||
|
||||
public override void ViewDidLoad ()
|
||||
{
|
||||
base.ViewDidLoad ();
|
||||
|
||||
appleMusicManager = new AppleMusicManager ();
|
||||
imageCacheManager = new ImageCacheManager ();
|
||||
mediaItems = new MediaItem [0] [];
|
||||
|
||||
// Configure self sizing cells.
|
||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||
TableView.EstimatedRowHeight = 100;
|
||||
|
||||
// Configure the `UISearchController`.
|
||||
searchController = new UISearchController (searchResultsController: null);
|
||||
searchController.SearchResultsUpdater = this;
|
||||
searchController.DimsBackgroundDuringPresentation = false;
|
||||
searchController.SearchBar.Delegate = this;
|
||||
TableView.TableHeaderView = searchController.SearchBar;
|
||||
DefinesPresentationContext = true;
|
||||
|
||||
/*
|
||||
* Add the notification observers needed to respond to events from
|
||||
* the `AuthorizationManager`, `MPMediaLibrary` and `UIApplication`.
|
||||
* This is so that if the user enables/disables capabilities in the
|
||||
* Settings app the application will reflect those changes accurately.
|
||||
*/
|
||||
var notificationCenter = NSNotificationCenter.DefaultCenter;
|
||||
authorizationDidUpdateNotificationToken = notificationCenter.AddObserver (AuthorizationManager.AuthorizationDidUpdateNotification,
|
||||
HandleAuthorizationManagerAuthorizationDidUpdateNotification,
|
||||
null);
|
||||
willEnterForegroundNotificationToken = notificationCenter.AddObserver (UIApplication.WillEnterForegroundNotification,
|
||||
HandleAuthorizationManagerAuthorizationDidUpdateNotification,
|
||||
null);
|
||||
}
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
// Remove all notification observers.
|
||||
var notificationCenter = NSNotificationCenter.DefaultCenter;
|
||||
notificationCenter.RemoveObserver (authorizationDidUpdateNotificationToken);
|
||||
notificationCenter.RemoveObserver (willEnterForegroundNotificationToken);
|
||||
|
||||
authorizationDidUpdateNotificationToken = willEnterForegroundNotificationToken = null;
|
||||
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
public override void ViewWillAppear (bool animated)
|
||||
{
|
||||
base.ViewWillAppear (animated);
|
||||
|
||||
if (appleMusicManager.FetchDeveloperToken () == null) {
|
||||
searchController.SearchBar.UserInteractionEnabled = false;
|
||||
|
||||
var alertController = UIAlertController.Create ("Error", "No developer token was specified. See the README for more information", UIAlertControllerStyle.Alert);
|
||||
alertController.AddAction (UIAlertAction.Create ("Dismiss", UIAlertActionStyle.Cancel, null));
|
||||
PresentViewController (alertController, true, null);
|
||||
} else
|
||||
searchController.SearchBar.UserInteractionEnabled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TableView Data Source
|
||||
|
||||
public override nint NumberOfSections (UITableView tableView) => mediaItems.Length;
|
||||
|
||||
public override nint RowsInSection (UITableView tableView, nint section) => mediaItems [section].Length;
|
||||
|
||||
public override string TitleForHeader (UITableView tableView, nint section) => section == 0 ? "Songs" : "Albums";
|
||||
|
||||
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
var cell = tableView.DequeueReusableCell (MediaItemTableViewCell.Key, indexPath) as MediaItemTableViewCell;
|
||||
|
||||
var mediaItem = mediaItems [indexPath.Section] [indexPath.Row];
|
||||
|
||||
cell.MediaItem = mediaItem;
|
||||
cell.Delegate = this;
|
||||
|
||||
// Image loading.
|
||||
var imageUrl = mediaItem.Artwork.GenerateImageUrl (new CGSize (90, 90));
|
||||
|
||||
if (imageCacheManager.GetCachedImage (imageUrl) is UIImage image)
|
||||
// Cached: set immediately.
|
||||
cell.AssetCoverArt = image;
|
||||
else {
|
||||
cell.AssetCoverArt = indexPath.Section == 0 ? UIImage.FromBundle ("Song") : UIImage.FromBundle ("Album");
|
||||
// Not cached, so load then fade it in.
|
||||
Task.Factory.StartNew (async () => {
|
||||
image = await imageCacheManager.FetchImage (imageUrl);
|
||||
|
||||
// Check the cell hasn't recycled while loading.
|
||||
if (cell.MediaItem.Id == mediaItem.Id)
|
||||
InvokeOnMainThread (() => cell.AssetCoverArt = image);
|
||||
});
|
||||
}
|
||||
|
||||
var cloudServceCapabilities = AuthorizationManager.CloudServiceCapabilities;
|
||||
|
||||
/* It is important to actually check if your application has the
|
||||
* appropriate `SKCloudServiceCapability` options before enabling
|
||||
* functionality related to playing back content from the Apple
|
||||
* Music Catalog or adding items to the user's Cloud Music Library.
|
||||
*/
|
||||
cell.AddToPlaylistButtonEnabled = cloudServceCapabilities.HasFlag (SKCloudServiceCapability.AddToCloudMusicLibrary);
|
||||
cell.PlayItemButtonEnabled = cloudServceCapabilities.HasFlag (SKCloudServiceCapability.MusicCatalogPlayback);
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notification Observing Methods
|
||||
|
||||
void HandleAuthorizationManagerAuthorizationDidUpdateNotification (NSNotification notification) =>
|
||||
TableView.ReloadData ();
|
||||
|
||||
#endregion
|
||||
|
||||
#region UISearchResultsUpdating
|
||||
|
||||
public void UpdateSearchResultsForSearchController (UISearchController searchController)
|
||||
{
|
||||
if (!(searchController.SearchBar.Text is string searchString))
|
||||
return;
|
||||
|
||||
if (searchString == string.Empty) {
|
||||
lock (padlock) {
|
||||
mediaItems = new MediaItem [0] [];
|
||||
}
|
||||
TableView.ReloadData ();
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Factory.StartNew (async () => {
|
||||
var searchResults = new MediaItem [0] [];
|
||||
try {
|
||||
searchResults = await appleMusicManager.PerformAppleMusicCatalogSearchAsync (searchString, AuthorizationManager.CloudServiceStorefrontCountryCode);
|
||||
} catch (NSErrorException ex) {
|
||||
var underlyingError = ex.Error.UserInfo [NSError.UnderlyingErrorKey] as NSError;
|
||||
var message = underlyingError?.LocalizedDescription ?? "Encountered unexpected error.";
|
||||
|
||||
var alertController = UIAlertController.Create ("Error", message, UIAlertControllerStyle.Alert);
|
||||
alertController.AddAction (UIAlertAction.Create ("Dismiss", UIAlertActionStyle.Cancel, null));
|
||||
|
||||
InvokeOnMainThread (() => PresentViewController (alertController, true, null));
|
||||
} finally {
|
||||
lock (padlock) {
|
||||
mediaItems = searchResults;
|
||||
}
|
||||
|
||||
InvokeOnMainThread (() => TableView.ReloadData ());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UISearchBar Delegate
|
||||
|
||||
[Export ("searchBarCancelButtonClicked:")]
|
||||
public void CancelButtonClicked (UISearchBar searchBar)
|
||||
{
|
||||
lock (padlock) {
|
||||
mediaItems = new MediaItem [0] [];
|
||||
}
|
||||
|
||||
TableView.ReloadData ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MediaSearchTableViewCell Delegate
|
||||
|
||||
public void AddToPlaylist (MediaItemTableViewCell mediaSearchTableViewCell, MediaItem mediaItem) =>
|
||||
Task.Factory.StartNew (async () => await MediaLibraryManager.AddItemAsync (mediaItem.Id));
|
||||
|
||||
public void PlayMediaItem (MediaItemTableViewCell mediaSearchTableViewCell, MediaItem mediaItem) =>
|
||||
MusicPlayerManager.BeginPlayback (mediaItem.Id);
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
20
ios11/MusicKitSample/MusicKitSample/Views/MediaSearchTableViewController.designer.cs
сгенерированный
Normal file
|
@ -0,0 +1,20 @@
|
|||
// WARNING
|
||||
//
|
||||
// This file has been generated automatically by Visual Studio to store outlets and
|
||||
// actions made in the UI designer. If it is removed, they will be lost.
|
||||
// Manual changes to this file may not be handled correctly.
|
||||
//
|
||||
using Foundation;
|
||||
using System.CodeDom.Compiler;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
[Register ("MediaSearchTableViewController")]
|
||||
partial class MediaSearchTableViewController
|
||||
{
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
// This file has been autogenerated from a class added in the UI designer.
|
||||
|
||||
using System;
|
||||
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using MusicKitSample.Controllers;
|
||||
using MediaPlayer;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
public partial class PlayerViewController : UIViewController
|
||||
{
|
||||
|
||||
#region Fields
|
||||
|
||||
NSObject didUpdateStateToken;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
// The instance of `MusicPlayerManager` used by the
|
||||
// `PlayerViewController` to control `MPMediaItem` playback.
|
||||
public MusicPlayerManager MusicPlayerManager { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public PlayerViewController (IntPtr handle) : base (handle)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Controller Life Cycle
|
||||
|
||||
public override void ViewDidLoad ()
|
||||
{
|
||||
base.ViewDidLoad ();
|
||||
|
||||
// Add the notification observer needed to respond to events
|
||||
// from the `MusicPlayerManager`.
|
||||
didUpdateStateToken = NSNotificationCenter.DefaultCenter.AddObserver (MusicPlayerManager.DidUpdateState,
|
||||
HandleMusicPlayerManagerDidUpdateState,
|
||||
null);
|
||||
|
||||
UpdatePlaybackControls ();
|
||||
UpdateCurrentItemMetadata ();
|
||||
}
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
NSNotificationCenter.DefaultCenter.RemoveObserver (didUpdateStateToken);
|
||||
didUpdateStateToken = null;
|
||||
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region User Interactions
|
||||
|
||||
partial void BtnSkipToPrevious_TouchUpInside (UIButton sender) => MusicPlayerManager.SkipBackToBeginningOrPreviousItem ();
|
||||
|
||||
partial void BtnPlayPause_TouchUpInside (UIButton sender) => MusicPlayerManager.TogglePlayPause ();
|
||||
|
||||
partial void BtnSkipToNext_TouchUpInside (UIButton sender) => MusicPlayerManager.SkipToNextItem ();
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI Update Methods
|
||||
|
||||
void UpdatePlaybackControls ()
|
||||
{
|
||||
var playbackState = MusicPlayerManager.MusicPlayerController.PlaybackState;
|
||||
|
||||
switch (playbackState) {
|
||||
case MPMusicPlaybackState.Stopped:
|
||||
case MPMusicPlaybackState.Paused:
|
||||
case MPMusicPlaybackState.Interrupted:
|
||||
BtnPlayPause.SetImage (UIImage.FromBundle ("Play"), UIControlState.Normal);
|
||||
break;
|
||||
case MPMusicPlaybackState.Playing:
|
||||
BtnPlayPause.SetImage (UIImage.FromBundle ("Pause"), UIControlState.Normal);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (playbackState == MPMusicPlaybackState.Stopped) {
|
||||
BtnSkipToPrevious.Enabled = false;
|
||||
BtnPlayPause.Enabled = false;
|
||||
BtnSkipToNext.Enabled = false;
|
||||
} else {
|
||||
BtnSkipToPrevious.Enabled = true;
|
||||
BtnPlayPause.Enabled = true;
|
||||
BtnSkipToNext.Enabled = true;
|
||||
}
|
||||
|
||||
UpdateCurrentItemMetadata ();
|
||||
}
|
||||
|
||||
void UpdateCurrentItemMetadata ()
|
||||
{
|
||||
if (MusicPlayerManager.MusicPlayerController.NowPlayingItem is MPMediaItem nowPlayingItem) {
|
||||
ImgCurrentArtwork.Image = nowPlayingItem.Artwork?.ImageWithSize (ImgCurrentArtwork.Frame.Size);
|
||||
LblCurrentTitle.Text = nowPlayingItem.Title;
|
||||
LblCurrentAlbum.Text = nowPlayingItem.AlbumTitle;
|
||||
LblCurrentArtist.Text = nowPlayingItem.Artist;
|
||||
} else {
|
||||
ImgCurrentArtwork.Image = null;
|
||||
LblCurrentTitle.Text = string.Empty;
|
||||
LblCurrentAlbum.Text = string.Empty;
|
||||
LblCurrentArtist.Text = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notification Observing Methods
|
||||
|
||||
void HandleMusicPlayerManagerDidUpdateState (NSNotification notification)
|
||||
{
|
||||
UpdatePlaybackControls ();
|
||||
UpdateCurrentItemMetadata ();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
94
ios11/MusicKitSample/MusicKitSample/Views/PlayerViewController.designer.cs
сгенерированный
Normal file
|
@ -0,0 +1,94 @@
|
|||
// WARNING
|
||||
//
|
||||
// This file has been generated automatically by Visual Studio from the outlets and
|
||||
// actions declared in your storyboard file.
|
||||
// Manual changes to this file will not be maintained.
|
||||
//
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
[Register ("PlayerViewController")]
|
||||
partial class PlayerViewController
|
||||
{
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIButton BtnPlayPause { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIButton BtnSkipToNext { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIButton BtnSkipToPrevious { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIImageView ImgCurrentArtwork { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UILabel LblCurrentAlbum { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UILabel LblCurrentArtist { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UILabel LblCurrentTitle { get; set; }
|
||||
|
||||
[Action ("BtnPlayPause_TouchUpInside:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void BtnPlayPause_TouchUpInside (UIKit.UIButton sender);
|
||||
|
||||
[Action ("BtnSkipToNext_TouchUpInside:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void BtnSkipToNext_TouchUpInside (UIKit.UIButton sender);
|
||||
|
||||
[Action ("BtnSkipToPrevious_TouchUpInside:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void BtnSkipToPrevious_TouchUpInside (UIKit.UIButton sender);
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
if (BtnPlayPause != null) {
|
||||
BtnPlayPause.Dispose ();
|
||||
BtnPlayPause = null;
|
||||
}
|
||||
|
||||
if (BtnSkipToNext != null) {
|
||||
BtnSkipToNext.Dispose ();
|
||||
BtnSkipToNext = null;
|
||||
}
|
||||
|
||||
if (BtnSkipToPrevious != null) {
|
||||
BtnSkipToPrevious.Dispose ();
|
||||
BtnSkipToPrevious = null;
|
||||
}
|
||||
|
||||
if (ImgCurrentArtwork != null) {
|
||||
ImgCurrentArtwork.Dispose ();
|
||||
ImgCurrentArtwork = null;
|
||||
}
|
||||
|
||||
if (LblCurrentAlbum != null) {
|
||||
LblCurrentAlbum.Dispose ();
|
||||
LblCurrentAlbum = null;
|
||||
}
|
||||
|
||||
if (LblCurrentArtist != null) {
|
||||
LblCurrentArtist.Dispose ();
|
||||
LblCurrentArtist = null;
|
||||
}
|
||||
|
||||
if (LblCurrentTitle != null) {
|
||||
LblCurrentTitle.Dispose ();
|
||||
LblCurrentTitle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// This file has been autogenerated from a class added in the UI designer.
|
||||
|
||||
using System;
|
||||
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
public partial class PlaylistTableViewCell : UITableViewCell
|
||||
{
|
||||
#region Cell Identifier
|
||||
|
||||
public static readonly NSString Key = new NSString ("PlaylistTableViewCell");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public UIImage AssetCoverArt {
|
||||
get { return ImgAssetCoverArt.Image; }
|
||||
set { ImgAssetCoverArt.Image = value; }
|
||||
}
|
||||
|
||||
public string Title {
|
||||
get { return LblTitle.Text; }
|
||||
set { LblTitle.Text = value; }
|
||||
}
|
||||
|
||||
public string Album {
|
||||
get { return LblAlbum.Text; }
|
||||
set { LblAlbum.Text = value; }
|
||||
}
|
||||
|
||||
public string Artist {
|
||||
get { return LblArtist.Text; }
|
||||
set { LblArtist.Text = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public PlaylistTableViewCell (IntPtr handle) : base (handle)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
55
ios11/MusicKitSample/MusicKitSample/Views/PlaylistTableViewCell.designer.cs
сгенерированный
Normal file
|
@ -0,0 +1,55 @@
|
|||
// WARNING
|
||||
//
|
||||
// This file has been generated automatically by Visual Studio from the outlets and
|
||||
// actions declared in your storyboard file.
|
||||
// Manual changes to this file will not be maintained.
|
||||
//
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
[Register ("PlaylistTableViewCell")]
|
||||
partial class PlaylistTableViewCell
|
||||
{
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UIImageView ImgAssetCoverArt { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UILabel LblAlbum { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UILabel LblArtist { get; set; }
|
||||
|
||||
[Outlet]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
UIKit.UILabel LblTitle { get; set; }
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
if (ImgAssetCoverArt != null) {
|
||||
ImgAssetCoverArt.Dispose ();
|
||||
ImgAssetCoverArt = null;
|
||||
}
|
||||
|
||||
if (LblAlbum != null) {
|
||||
LblAlbum.Dispose ();
|
||||
LblAlbum = null;
|
||||
}
|
||||
|
||||
if (LblArtist != null) {
|
||||
LblArtist.Dispose ();
|
||||
LblArtist = null;
|
||||
}
|
||||
|
||||
if (LblTitle != null) {
|
||||
LblTitle.Dispose ();
|
||||
LblTitle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// This file has been autogenerated from a class added in the UI designer.
|
||||
|
||||
using System;
|
||||
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using MusicKitSample.Controllers;
|
||||
using StoreKit;
|
||||
using CoreGraphics;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
public partial class PlaylistTableViewController : UITableViewController
|
||||
{
|
||||
#region Fields
|
||||
|
||||
NSObject libraryDidUpdateToken;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
// The instance of `AuthorizationManager` used for querying and
|
||||
// requesting authorization status.
|
||||
public AuthorizationManager AuthorizationManager { get; set; }
|
||||
|
||||
// The instance of `MediaLibraryManager` that is used as a data
|
||||
// source to display the contents of the application's playlist.
|
||||
public MediaLibraryManager MediaLibraryManager { get; set; }
|
||||
|
||||
// The instance of `MusicPlayerManager` that is used to trigger
|
||||
// the playback of the application's playlist.
|
||||
public MusicPlayerManager MusicPlayerManager { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public PlaylistTableViewController (IntPtr handle) : base (handle)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Controller Life Cycle
|
||||
|
||||
public override void ViewDidLoad ()
|
||||
{
|
||||
base.ViewDidLoad ();
|
||||
|
||||
// Configure self sizing cells.
|
||||
TableView.RowHeight = UITableView.AutomaticDimension;
|
||||
TableView.EstimatedRowHeight = 140;
|
||||
|
||||
// Add the notification observer needed to respond to events
|
||||
// from the `MediaLibraryManager`.
|
||||
libraryDidUpdateToken = NSNotificationCenter.DefaultCenter.AddObserver (MediaLibraryManager.LibraryDidUpdate,
|
||||
HandleMediaLibraryManagerLibraryDidUpdate,
|
||||
null);
|
||||
}
|
||||
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
NSNotificationCenter.DefaultCenter.RemoveObserver (libraryDidUpdateToken);
|
||||
libraryDidUpdateToken = null;
|
||||
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
public override void ViewWillAppear (bool animated)
|
||||
{
|
||||
base.ViewWillAppear (animated);
|
||||
|
||||
TableView.ReloadData ();
|
||||
|
||||
/*
|
||||
* It is important to actually check if your application has the
|
||||
* appropriate `SKCloudServiceCapability` options before enabling
|
||||
* functionality related to playing back content from the Apple
|
||||
* Music Catalog or adding items to the user's Cloud Music Library.
|
||||
*/
|
||||
var cloudServiceCapabilities = AuthorizationManager.CloudServiceCapabilities;
|
||||
NavigationItem.RightBarButtonItem.Enabled = cloudServiceCapabilities.HasFlag (SKCloudServiceCapability.MusicCatalogPlayback);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TableView Data Source
|
||||
|
||||
public override nint NumberOfSections (UITableView tableView) => 1;
|
||||
|
||||
public override nint RowsInSection (UITableView tableView, nint section) => MediaLibraryManager.MediaPlaylist?.Items.Length ?? 0;
|
||||
|
||||
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
var cell = tableView.DequeueReusableCell (PlaylistTableViewCell.Key, indexPath) as PlaylistTableViewCell;
|
||||
var mediaItem = MediaLibraryManager.MediaPlaylist.Items [indexPath.Row];
|
||||
|
||||
cell.Title = mediaItem.Title;
|
||||
cell.Album = mediaItem.AlbumTitle;
|
||||
cell.Artist = mediaItem.Artist;
|
||||
cell.AssetCoverArt = mediaItem.Artwork?.ImageWithSize (new CGSize (90, 90));
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region User Interactions
|
||||
|
||||
partial void BtnPlayPlaylist_Tapped (UIBarButtonItem sender) => MusicPlayerManager.BeginPlayback (MediaLibraryManager.MediaPlaylist);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notification Observer Callback Methods
|
||||
|
||||
void HandleMediaLibraryManagerLibraryDidUpdate (NSNotification notification) => TableView.ReloadData ();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
24
ios11/MusicKitSample/MusicKitSample/Views/PlaylistTableViewController.designer.cs
сгенерированный
Normal file
|
@ -0,0 +1,24 @@
|
|||
// WARNING
|
||||
//
|
||||
// This file has been generated automatically by Visual Studio from the outlets and
|
||||
// actions declared in your storyboard file.
|
||||
// Manual changes to this file will not be maintained.
|
||||
//
|
||||
using Foundation;
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
[Register ("PlaylistTableViewController")]
|
||||
partial class PlaylistTableViewController
|
||||
{
|
||||
[Action ("BtnPlayPlaylist_Tapped:")]
|
||||
[GeneratedCode ("iOS Designer", "1.0")]
|
||||
partial void BtnPlayPlaylist_Tapped (UIKit.UIBarButtonItem sender);
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
// This file has been autogenerated from a class added in the UI designer.
|
||||
|
||||
using System;
|
||||
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using MusicKitSample.Controllers;
|
||||
using MusicKitSample.Models;
|
||||
using CoreGraphics;
|
||||
using System.Threading.Tasks;
|
||||
using StoreKit;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
public partial class RecentlyPlayedTableViewController : UITableViewController, IMediaSearchTableViewCellDelegate
|
||||
{
|
||||
#region Fields
|
||||
|
||||
static readonly object padlock = new object ();
|
||||
|
||||
ImageCacheManager imageCacheManager;
|
||||
MediaItem [] mediaItems;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public AuthorizationManager AuthorizationManager { get; set; }
|
||||
public AppleMusicManager AppleMusicManager { get; set; }
|
||||
public MusicPlayerManager MusicPlayerManager { get; set; }
|
||||
public MediaLibraryManager MediaLibraryManager { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public RecentlyPlayedTableViewController (IntPtr handle) : base (handle)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Controller Life Cycle
|
||||
|
||||
public override void ViewDidLoad ()
|
||||
{
|
||||
base.ViewDidLoad ();
|
||||
|
||||
imageCacheManager = new ImageCacheManager ();
|
||||
mediaItems = new MediaItem [0];
|
||||
}
|
||||
|
||||
public override void ViewWillAppear (bool animated)
|
||||
{
|
||||
base.ViewWillAppear (animated);
|
||||
|
||||
if (AppleMusicManager.FetchDeveloperToken () == null) {
|
||||
var alertController = UIAlertController.Create ("Error",
|
||||
"No Developer Token was specified. See the README for more information.",
|
||||
UIAlertControllerStyle.Alert);
|
||||
alertController.AddAction (UIAlertAction.Create ("Dismiss", UIAlertActionStyle.Cancel, null));
|
||||
PresentViewController (alertController, true, null);
|
||||
} else if (AuthorizationManager.UserToken == string.Empty) {
|
||||
var alertController = UIAlertController.Create ("Error",
|
||||
"No User Token was specified. Request Authorization using the \"Authorization\" tab.",
|
||||
UIAlertControllerStyle.Alert);
|
||||
alertController.AddAction (UIAlertAction.Create ("Dismiss", UIAlertActionStyle.Cancel, null));
|
||||
PresentViewController (alertController, true, null);
|
||||
} else {
|
||||
Task.Factory.StartNew (async () => await RefreshData ());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TableView Source
|
||||
|
||||
public override nint NumberOfSections (UITableView tableView) => 1;
|
||||
|
||||
public override nint RowsInSection (UITableView tableView, nint section) => mediaItems.Length;
|
||||
|
||||
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
|
||||
{
|
||||
var cell = tableView.DequeueReusableCell (MediaItemTableViewCell.Key, indexPath) as MediaItemTableViewCell;
|
||||
var mediaItem = mediaItems [indexPath.Row];
|
||||
|
||||
cell.MediaItem = mediaItem;
|
||||
cell.Delegate = this;
|
||||
|
||||
// Image loading.
|
||||
var imageUrl = mediaItem.Artwork.GenerateImageUrl (new CGSize (90, 90));
|
||||
|
||||
if (imageCacheManager.GetCachedImage (imageUrl) is UIImage image)
|
||||
// Cached: set immediately.
|
||||
cell.AssetCoverArt = image;
|
||||
else {
|
||||
// Not cached, so load then fade it in.
|
||||
Task.Factory.StartNew (async () => {
|
||||
image = await imageCacheManager.FetchImage (imageUrl);
|
||||
|
||||
// Check the cell hasn't recycled while loading.
|
||||
if (cell.MediaItem.Id == mediaItem.Id)
|
||||
InvokeOnMainThread (() => cell.AssetCoverArt = image);
|
||||
});
|
||||
}
|
||||
|
||||
var cloudServceCapabilities = AuthorizationManager.CloudServiceCapabilities;
|
||||
|
||||
/* It is important to actually check if your application has the
|
||||
* appropriate `SKCloudServiceCapability` options before enabling
|
||||
* functionality related to playing back content from the Apple
|
||||
* Music Catalog or adding items to the user's Cloud Music Library.
|
||||
*/
|
||||
cell.AddToPlaylistButtonEnabled = cloudServceCapabilities.HasFlag (SKCloudServiceCapability.AddToCloudMusicLibrary);
|
||||
cell.PlayItemButtonEnabled = cloudServceCapabilities.HasFlag (SKCloudServiceCapability.MusicCatalogPlayback);
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Notification Observer Callback Methods.
|
||||
|
||||
async Task RefreshData ()
|
||||
{
|
||||
// Your application should handle these errors appropriately depending on the kind of error.
|
||||
var items = new MediaItem [0];
|
||||
try {
|
||||
items = await AppleMusicManager.PerformAppleMusicGetRecentlyPlayedAsync (AuthorizationManager.UserToken);
|
||||
} catch (NSErrorException ex) {
|
||||
var underlyingError = ex.Error.UserInfo [NSError.UnderlyingErrorKey] as NSError;
|
||||
var message = underlyingError?.LocalizedDescription ?? "Encountered unexpected error.";
|
||||
|
||||
var alertController = UIAlertController.Create ("Error", message, UIAlertControllerStyle.Alert);
|
||||
alertController.AddAction (UIAlertAction.Create ("Dismiss", UIAlertActionStyle.Cancel, null));
|
||||
|
||||
InvokeOnMainThread (() => PresentViewController (alertController, true, null));
|
||||
} finally {
|
||||
lock (padlock) {
|
||||
mediaItems = items;
|
||||
}
|
||||
InvokeOnMainThread (() => TableView.ReloadData ());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MediaItemTableViewCell Delegate
|
||||
|
||||
public void AddToPlaylist (MediaItemTableViewCell mediaSearchTableViewCell, MediaItem mediaItem) =>
|
||||
Task.Factory.StartNew (async () => await MediaLibraryManager.AddItemAsync (mediaItem.Id));
|
||||
|
||||
public void PlayMediaItem (MediaItemTableViewCell mediaSearchTableViewCell, MediaItem mediaItem) =>
|
||||
MusicPlayerManager.BeginPlayback (mediaItem.Id);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
20
ios11/MusicKitSample/MusicKitSample/Views/RecentlyPlayedTableViewController.designer.cs
сгенерированный
Normal file
|
@ -0,0 +1,20 @@
|
|||
// WARNING
|
||||
//
|
||||
// This file has been generated automatically by Visual Studio to store outlets and
|
||||
// actions made in the UI designer. If it is removed, they will be lost.
|
||||
// Manual changes to this file may not be handled correctly.
|
||||
//
|
||||
using Foundation;
|
||||
using System.CodeDom.Compiler;
|
||||
|
||||
namespace MusicKitSample
|
||||
{
|
||||
[Register ("RecentlyPlayedTableViewController")]
|
||||
partial class RecentlyPlayedTableViewController
|
||||
{
|
||||
|
||||
void ReleaseDesignerOutlets ()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MusicKitTokenGenerator", "MusicKitTokenGenerator\MusicKitTokenGenerator.csproj", "{F0C7C922-CAFA-4D0A-8742-4E11633ED6A6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F0C7C922-CAFA-4D0A-8742-4E11633ED6A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F0C7C922-CAFA-4D0A-8742-4E11633ED6A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F0C7C922-CAFA-4D0A-8742-4E11633ED6A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F0C7C922-CAFA-4D0A-8742-4E11633ED6A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.1.3" />
|
||||
<PackageReference Include="jose-jwt" Version="2.4.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
using Jose;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.OpenSsl;
|
||||
|
||||
namespace MusicKitTokenGenerator
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine ("----- GENERATING TOKEN -----");
|
||||
|
||||
var token = GenerateToken ();
|
||||
|
||||
Console.WriteLine ("----- GENERATED TOKEN -----");
|
||||
Console.WriteLine ($"{token}\n");
|
||||
}
|
||||
|
||||
static string GenerateToken()
|
||||
{
|
||||
var algorithm = "ES256";
|
||||
|
||||
/*
|
||||
* The Key ID of your MusicKit Private key:
|
||||
* https://developer.apple.com/account/ios/authkey/
|
||||
* For more information, go to Apple Music API Reference:
|
||||
* https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/AppleMusicWebServicesReference/SetUpWebServices.html#//apple_ref/doc/uid/TP40017625-CH2-SW1
|
||||
*/
|
||||
var keyId = "«MusicKit_Private_Key_ID»";
|
||||
Console.WriteLine ($"Your MusicKit Private Key ID: {keyId}");
|
||||
|
||||
// Your Team ID from your Apple Developer Account:
|
||||
// https://developer.apple.com/account/#/membership/
|
||||
var teamId = "«Team_ID»";
|
||||
Console.WriteLine ($"Your Apple Team ID: {keyId}");
|
||||
|
||||
var utcNow = DateTime.UtcNow;
|
||||
var epoch = new DateTime (1970, 1, 1);
|
||||
var epochNow = (int)utcNow.Subtract (epoch).TotalSeconds;
|
||||
var utcExpire = utcNow.AddMonths (6);
|
||||
var epochExpire = (int)utcExpire.Subtract(epoch).TotalSeconds;
|
||||
|
||||
Console.WriteLine ($"The Token was issued at (UTC Time): {utcNow.ToString ("yyyy/MM/dd")}");
|
||||
Console.WriteLine ($"The Token will expire at (UTC Time): {utcExpire.ToString ("yyyy/MM/dd")}\n");
|
||||
|
||||
var headers = new Dictionary<string, object>
|
||||
{
|
||||
{ "alg", algorithm },
|
||||
{ "kid", keyId }
|
||||
};
|
||||
var payload = new Dictionary<string, object>
|
||||
{
|
||||
{ "iss", teamId },
|
||||
{ "iat", epochNow },
|
||||
{ "exp", epochExpire }
|
||||
};
|
||||
|
||||
var headersString = string.Join ($",\n{" ",4}", headers.Select (kv => $"{kv.Key}: {kv.Value}"));
|
||||
Console.WriteLine ($"Headers to be encoded:");
|
||||
Console.WriteLine ($"{{\n{" ",4}{headersString}\n}}\n");
|
||||
|
||||
var payloadString = string.Join ($",\n{" ",4}", payload.Select (kv => $"{kv.Key}: {kv.Value}"));
|
||||
Console.WriteLine ($"Payload to be encoded:");
|
||||
Console.WriteLine ($"{{\n{" ", 4}{payloadString}\n}}\n");
|
||||
|
||||
var parameters = GetPrivateParameters();
|
||||
var secretKey = ECDsa.Create (parameters);
|
||||
var token = JWT.Encode (payload, secretKey, JwsAlgorithm.ES256, headers);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
static ECParameters GetPrivateParameters()
|
||||
{
|
||||
using (var reader = File.OpenText("/path/to/your/MusicKit_Secret_Key.p8"))
|
||||
{
|
||||
var ecPrivateKeyParameters = (ECPrivateKeyParameters)new PemReader (reader).ReadObject ();
|
||||
var x = ecPrivateKeyParameters.Parameters.G.AffineXCoord.GetEncoded ();
|
||||
var y = ecPrivateKeyParameters.Parameters.G.AffineYCoord.GetEncoded ();
|
||||
var d = ecPrivateKeyParameters.D.ToByteArrayUnsigned ();
|
||||
|
||||
var parameters = new ECParameters {
|
||||
Curve = ECCurve.NamedCurves.nistP256,
|
||||
D = d,
|
||||
Q = new ECPoint {
|
||||
X = x,
|
||||
Y = y
|
||||
}
|
||||
};
|
||||
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
MusicKitSample
|
||||
==============
|
||||
|
||||
MusicKit on iOS lets users play Apple Music and their local music library natively from your apps and games. When a user provides permission to their Apple Music account, your app can create playlists, add songs to their library, and play any of the millions of songs in the Apple Music catalog. If your app detects that the user is not yet an Apple Music member, you can offer a trial from within your app.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Xamarin port changes are released under the MIT license.
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
Ported to Xamarin.iOS by Israel Soto.
|
После Ширина: | Высота: | Размер: 1.0 MiB |
После Ширина: | Высота: | Размер: 1.6 MiB |
После Ширина: | Высота: | Размер: 1.2 MiB |
После Ширина: | Высота: | Размер: 1.4 MiB |
После Ширина: | Высота: | Размер: 1.4 MiB |