Unable to cast music to Chromecast - ios

I'm trying to establish a simple audio stream to a Chromecast device. This is just a POC for me to familiarize myself with the API. What I want to do is load up the user's library, select a song, and have it cast over. I've been following the integration guide quite closely but to no avail.
Please find the full project on Github here.
An overview of my code, AppDelegate.m:
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
GCKCastOptions *options = [GCKCastOptions.alloc initWithReceiverApplicationID:kGCKMediaDefaultReceiverApplicationID];
[GCKCastContext setSharedInstanceWithOptions:options];
[GCKCastContext.sharedInstance.sessionManager addListener:self];
}
UITableViewDelegate:
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
MPMediaItem *mediaItem = MusicManager.sharedManager[index];
NSString *path = [MPMediaLibrary.defaultMediaLibrary pathForAssetURL:mediaItem.assetURL];
GCKMediaMetadata *metadata = [GCKMediaMetadata.alloc initWithMetadataType:GCKMediaMetadataTypeMusicTrack];
[metadata setString:mediaItem.title forKey:kGCKMetadataKeyTitle];
[metadata setString:mediaItem.artist forKey:kGCKMetadataKeyArtist];
[metadata setString:mediaItem.albumTitle forKey:kGCKMetadataKeyAlbumTitle];
GCKMediaInformation *mediaInfo = [GCKMediaInformation.alloc initWithContentID:path
streamType:GCKMediaStreamTypeBuffered
contentType:[self fileMIMEType:path]
metadata:metadata
streamDuration:mediaItem.playbackDuration
customData:nil];
[GCKCastContext.sharedInstance.sessionManager.currentCastSession.remoteMediaClient loadMedia:mediaInfo autoplay:YES];
}
I initiate the casting session by tapping the GCKCastButton and Start Session, my TV shows the Cast logo, then when I tap on a specific song, my TV briefly shows the metadata (i.e. song title, artist name, etc.), and then reverts back to the Chromecast logo. On the device, if I remain on the screen presented by GCKCastButton, I can also see the details of the song that is supposed to be currently casting, but quickly changes to "No Media Selected" after a few seconds.
I've checked the file path, the MIME type, everything is correct and playable. I've even tried bundling a short MP3 and trying to cast that, but to no avail.
Can't help but feel like I'm missing something here, the integration guide doesn't really give much more info.
Any insight is appreciated. Thanks!

It seems that I was under the wrong impression that the cast library initiates a stream from the provided file itself. But instead it required an actual URL for the Chromecast device to perform a GET operation on.
I imagine this will be solved by running a server locally on the iOS device.

Related

iOS Document Sharing: "Save to Dropbox" always fails

Posting after finding answer
After "rubber duck debugging" this answer a bunch, I finally came across the correct answer on a question that appears to me to be unrelated. I think this question (and its answer) are still relevant, so I'm posting the question and will post my own answer to hopefully help others like me.
I am creating a PDF in my iOS app that I would like to allow the user to export. For the purposes of this testing, I'm trying to save it to my personal Dropbox on a physical device.
I have turned on iTunes file sharing, and I can verify that the PDF file is being generated correctly, and when I copy it off of my device (iPad Pro Gen. 2 running iOS 11), I can open the PDF and it has the expected content and appearance.
I am able to get the document pop-up to display correctly, and I have options to share via:
Line 1: AirDrop
Line 2: Message, Mail, Add to Notes, (Facebook) Messenger, etc.
Line 3: Copy, Print, Save to Files, Save to Dropbox, etc.
No matter what I try to select (Save to Dropbox is the one I want to solve, but the issue seems universal), it fails. Of note, when I click Save to Dropbox, I do see the Dropbox panel display, but there is immediately a modal over top of the Save to Dropbox modal that says, "An unknown error occurred."
I have tried to look around and see how to get more information about this error, but I'm stumped. I'm not sure if it's correlated, but I get this message in the console:
[AXRun-PID] Client requesting unsuspension of PID:813 Name:<redacted>
Trying to google that error has proved unfruitful.
Here's the code where I generate the PDF and show the menu:
#pragma mark • Sharing Methods
- (void)showShareMenu {
NSArray *bookList = [BookManager bookList];
NSURL *pdfUrl = [PdfGenerator generatePdfFromBooks:bookList];
UIDocumentInteractionController *vc = [[UIDocumentInteractionController alloc] init];
vc.name = #"Booklet.pdf";
vc.URL = pdfUrl;
vc.UTI = #"com.adobe.pdf";
[vc presentOptionsMenuFromBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];
}
I've tried using UIDocumentInteractionController *vc = [UIDocumentInteractionController interactionControllerWithURL:pdfUrl]; instead of the one above, but the results are the same.
I tried making self the delegate of vc and then tried to implement the following methods:
- (void)documentInteractionController:(UIDocumentInteractionController *)controller
willBeginSendingToApplication:(nullable NSString *)application;
- (void)documentInteractionController:(UIDocumentInteractionController *)controller
didEndSendingToApplication:(nullable NSString *)application;
Neither of those methods ever fired.
Interestingly, though I think I've supplied the file name correctly based on what I've read, the name in the File textbook in the Save to Dropbox modal is a current timestamp (e.g., File Oct 28, 11 12 22 PM). The Dropbox modal stays up until I click "OK" on the "An unknown error occurred" modal, and then disappears immediately.
It seems like I'm somehow not providing the right information, but I'm not sure how. It seems like there ought to be a delegate method to indicate an error to me, but I don't see anything like that in the docs. (It is late, and I have been looking at this for hours, including reading several related tutorials, so I could have missed something obvious.)
I came across this answer as an example question while asking this current question.
It doesn't really ask the same question I have, nor did that user have the same error outputs I did. But, the linked answer did work for me, too.
The problem I had in the code above was that I was not keeping the UIDocumentInteractionController around after I created it. Adding a private property fixed this issue. So, the following code now works:
#pragma mark • Sharing Methods
- (void)showShareMenu {
NSArray *bookList = [BookManager bookList];
NSURL *pdfUrl = [PdfGenerator generatePdfFromBooks:bookList];
self.docController = [UIDocumentInteractionController interactionControllerWithURL:pdfUrl];
self.docController.name = #"Booklet.pdf";
self.docController.UTI = #"com.adobe.pdf";
[self.docController presentOptionsMenuFromBarButtonItem:self.navigationItem.leftBarButtonItem animated:YES];
}

Update MediaItemDisplayProperties on the fly

i have created a UWP App which uses HTML5 Webradio streams.
Everything works fine but now i wanted to add track and artist information to the MediaPlayer Element.
This information will be shown if the user locked his device, on the start screen.
The first track if the user selects a stream is shown correctly. But I can't update this information without restart the Stream.
MediaItemDisplayProperties mdp = _mediaPlaybackItem.GetDisplayProperties();
mdp.Type = Windows.Media.MediaPlaybackType.Music;
mdp.MusicProperties.Artist = "TBA Artist";
mdp.MusicProperties.Title = "TBA Title";
mdp.Thumbnail = Windows.Storage.Streams.RandomAccessStreamReference.CreateFromUri(MainPage.Current.CurrentStream.PreviewImageUri);
_mediaPlaybackItem.ApplyDisplayProperties(mdp);
_mediaPlayer.Source = mpItem;
_mediaPlayer.Play();
If i take this lines into my refresh Method for Artist/Title, I also have to set the Source of _mediaPlayer again which will result to a pause of playing the music.
Does anyone have an idea how to fix this problem? Or give any advice I can look further.
Thanks Chris
If you want to update the Artist/Title, you should be able to use System​Media​Transport​Controls​Display​Updater class, it provides functionality to update the music information that is displayed on the SystemMediaTransportControls.
We can set Artist/Title to the SystemMediaTransportControlsDisplayUpdater.MusicProperties property. Then we can use SystemMediaTransportControlsDisplayUpdater.Update method to update the metadata for the currently playing media.
Use the SystemMediaTransportControlsDisplayUpdater class to update the media info that is displayed by the transport controls, such as the song title or the album art for the currently playing media item. Get an instance of this class with the SystemMediaTransportControls.DisplayUpdater property. If your scenario requires it, you can update the metadata displayed by the system media transport controls manually by setting the values of the MusicProperties, ImageProperties, or VideoProperties objects exposed by the DisplayUpdater class.
For example:
SystemMediaTransportControlsDisplayUpdater updater = _systemMediaTransportControls.DisplayUpdater;
updater.MusicProperties.Artist = "artist";
updater.MusicProperties.AlbumArtist = "album artist";
updater.MusicProperties.Title = "song title";
updater.Thumbnail = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///Music/music1_AlbumArt.jpg"));
updater.Update();

MPMediaItemPropertyAssetURL returning nil

I am working on a project where I need to play songs from iTunes Library in AVPlayer. For that, I am taking URL "ipod-library://item/item.mp3?id=1577682869916034242" of selected songs from iTunes Library and playing same in AVPlayer. Almost all songs get play, but for few songs MPMediaItemPropertyAssetURL returns nil URL. Also receive following error
-[AVAssetReader initWithAsset:error:] invalid parameter not satisfying: asset != ((void *)0)
Any suggestion on this? and why I am getting nil value from
MPMediaItemPropertyAssetURL
Also any idea how to stream or convert DRM Protected Media track into NSData?.
Please advice.
MPMediaItemPropertyAssetURL can returns null for two possible reason.
The music is not downloaded to your device but added in music
library only.
The music is loaded but its DRM-protected.
DRM-protected asset is not possible to play using AVPlayer, its only able to play using MPMusicPlayer. So you must need to check two things before proceed with AVPlayer.
MPMediaItemPropertyAssetURL is nil ?
MPMediaItem is protected ?
Please see the code below….
MPMediaItem *theChosenSong = [[mediaItemCollection items] firstObject];
NSURL *assetURL = [theChosenSong valueForProperty:MPMediaItemPropertyAssetURL];
if(assetURL) {
BOOL bIsProtected = theChosenSong.protectedAsset;
if(!bIsProtected) {
// Do whatever you want to do
NSLog(#"Its not protected");
}
else {
NSLog(#"Its DRM protected");
}
}
else {
NSLog(#"DRM protected or not downloaded locally");
}
I found out that the problem was the song I was trying to get the MPMediaItemPropertyAssetURL property for was actually not on my device. It was listed in the media library, but was actually still in iCloud. Once I downloaded the song to my device then the problem was solved.
Leaving this answer for other people like me.
Even though the music is on downloaded on the device, if it is DRM protected, MPMediaItem.value(forProperty: MPMediaItemPropertyAssetURL) will return nil as mentioned in the comment.
My app kept crashing and I confirmed this with my beta tester.
It seems like MPMusicPlayerController still supports playback so according to this answer.

Stream video while downloading iOS

I am using iOS 7 and I have a .mp4 video that I need to download in my app. The video is large (~ 1 GB) which is why it is not included as part of the app. I want the user to be able to start watching the video as soon as is starts downloading. I also want the video to be able to be cached on the iOS device so the user doesn't need to download it again later. Both the normal methods of playing videos (progressive download and live streaming) don't seem to let you cache the video, so I have made my own web service that chunks up my video file and streams the bytes down to the client. I start the streaming HTTP call using NSURLConnection:
self.request = [[NSMutableURLRequest alloc] initWithURL:self.url];
[self.request setTimeoutInterval:10]; // Expect data at least every 10 seconds
[self.request setHTTPMethod:#"GET"];
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:YES];
When I receive a data chunk, I append it to the end of the local copy of the file:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:[self videoFilePath]];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
[handle writeData:data];
}
If I let the device run, the file is downloaded successfully and I can play it using MPMoviePlayerViewController:
NSURL *url=[NSURL fileURLWithPath:self.videoFilePath];
MPMoviePlayerViewController *controller = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
controller.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
[self presentMoviePlayerViewControllerAnimated:controller];
However, if I start the player before the file is completely downloaded, the video starts playing just fine. It even has the correct video length displayed at the top scrubber bar. But when the user gets to the position in the video that I had completed downloading before the video started, the video just hangs. If I close and reopen the MPMoviePlayerViewController, then the video plays until it gets to whatever location I was then at when I launched the MPMoviePlayerViewController again. If I wait until the entire video is downloaded, then the video plays without a problem.
I am not getting any events fired, or error messages printed to the console when this happens (MPMoviePlayerPlaybackStateDidChangeNotification and MPMoviePlayerPlaybackDidFinishNotification are never sent after the video starts). It seems like there is something else that is telling the controller what the length of the video is other than what the scrubber is using...
Does anyone know what could be causing this issue? I am not bound to using MPMoviePlayerViewController, so if a different video playback method would work in this situation I am all for it.
Related Unresolved Questions:
AVPlayer and Progressive Video Downloads with AVURLAssets
Progressive Video Download on iOS
How to play an in downloading progress video file in IOS
UPDATE 1
I have found that the video stall is indeed because of the file size when the video starts playing. I can get around this issue by creating a zero-ed out file before I start the download and over overwrite it as I go. Since I have control over the video streaming server, I added a custom header so I know the size of the file being streamed (default file size header for a streaming file is -1). I am creating the file in my didReceiveResponse method as follows:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// Retrieve the size of the file being streamed.
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSDictionary *headers = httpResponse.allHeaderFields;
NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
self.streamingFileSize = [formatter numberFromString:[headers objectForKey:#"StreamingFileSize"]];
// Check if we need to initialize the download file
if (![[NSFileManager defaultManager] fileExistsAtPath:self.path])
{
// Create the file being downloaded
[[NSData data] writeToFile:self.path atomically:YES];
// Allocate the size of the file we are going to download.
const char *cString = [self.path cStringUsingEncoding:NSASCIIStringEncoding];
int success = truncate(cString, self.streamingFileSize.longLongValue);
if (success != 0)
{
/* TODO: handle errors here. Probably not enough space... See 'man truncate' */
}
}
}
This works great, except that truncate causes the app to hang for about 10 seconds while it creates the ~1GB file on disk (on the simulator it is instant, only a real device has this problem). This is where I am stuck now - does anyone know of a way to allocate a file more efficiently, or a different way to get the video player to recognize the size of the file without needing to actually allocate it? I know some filesystems support "file size" and "size on disk" as two different properties... not sure if iOS has something like that?
I figured out how to do this, and it is much simpler than my original idea.
First, since my video is in .mp4, the MPMoviePlayerViewController or AVPlayer class can play it directly from a web server - I don't need to implement anything special and they can still seek to any point in the video. This must be part of how the .mp4 encoding works with the movie players. So, I just have the raw file available on the server - no special headers required.
Next, when the user decides to play the video I immediately start playing the video from the server URL:
NSURL *url=[NSURL fileURLWithPath:serverVidelFileURLString];
controller = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
controller.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
[self presentMoviePlayerViewControllerAnimated:controller];
This makes it so the user can watch the video and seek to any location they want. Then, I start downloading the file manually using NSURLConnection like I had been doing above, except now I am not streaming the file, I just download it directly. This way I don't need the custom header since the file size is included in the HTTP response.
When my background download completes, I switch the playing item from the server URL to the local file. This is important for network performance because the movie players only download a few seconds ahead of what the user is watching. Being able to switch to the local file as soon as possible is key to avoid downloading too much duplicate data:
NSTimeInterval currentPlaybackTime = videoController.moviePlayer.currentPlaybackTime;
[controller.moviePlayer setContentURL:url];
[controller.moviePlayer setCurrentPlaybackTime:currentPlaybackTime];
[controller.moviePlayer play];
This method does have the user downloading two video files at the same time initially, but initial testing on the network speeds my users will be using shows it only increases the download time by a few seconds. Works for me!
You gotta create an internal webserver that acts like a proxy! Then set your player to play the movie from the localhost.
When using HTTP protocol to play a video with MPMoviePlayerViewController, the first thing the player does is to ask for the byte-range 0-1 (first 2 bytes) just to obtain the file length. Then, the player asks for "chunks" of the video using the "byte-range" HTTP command (the purpose is to save some battery).
What you have to do is to implement this internal server that delivers the video to the player, but your "proxy" must consider the length of your video as the full length of the file, even if the actual file hasn't been completely downloaded from the internet.
Then you you set your player to play a movie from " http:// localhost : someport "
I've done this before... it works perfectly!
Good luck!
I can only assume that the MPMoviePlayerViewController caches the file length of the file when you started it.
The way to fix (just) this issue is to first determine how large the file is. Then create a file of that length. Keeping an offset pointer, as the file downloads, you can overwrite the "null" values in the file with the real data.
So you get to a specific point in the download, start the MPMoviePlayerViewController, and let it run. I'd also suggest you use the "F_NOCACHE" flag (with fcntl()) so you bypass the file block cache (which means you will lower your memory footprint).
The downside to this architecture is that if you get stalled, and the movie player gets ahead of you, well, the user is going to have a pretty bad experience. Not sure if there is any way for you to monitor and take preemptive action.
EDIT: its quite possible that the video is not read sequentially, but certain information requires the player to essentially look ahead for something. If so, then this is doomed to fail. The only other possible solution is to use some software tool to sequentially order the file (I'm no video expert so cannot comment from experience on any of the above).
To test this out, you can construct a "damaged" video of varying lengths, and test that to see what works and what does not. For instance, suppose you have a 100Meg file. Write a little utility program, and over write the last 50Megs of data with zeros. Now play this video. Its should fail 1/2 through. If it fails right away, well, you now know that its seeking in the file.
If non sequential, its possible that its looking at the last 1000 bytes or so, in which case if you don't overwrite that things work as you want. If you get lucky and this is the case, you would eventually download the last 1000 bytes, then then start from the front of the file.
It really gets down to finding some way before introducing real networking into the picture, to play a partial file. You will surely find it easier to artificially introduce the networking conditions without really doing it real time.

Flex/Flash Builder/Actionscript/AIR/Mobile iOS How to take video using the camera and/or browse for & view/access video stored in the 'Camera Roll"

My understanding currently is that:
CameraUI
I can use the CameraUI to access the built in camera for MediaType.VIDEO and that delegates to the built-in video camera app and lets me record a video. My app does that now.
When I stop recording and click the "Use" button, I am returned to my app and theoretically I have a valid MediaPromise.
iOS does -not- provide a valid/usable url/filename to the recorded video (or to photos) and so I would have to use a Loader to bring-in/use/access the 'recorded' video... AND... iOS does not actually create a file anywhere on the device, most importantly, in the Camera Roll where one would expect by the normal behavior when uses the system native camera/video app.
The documentation says that the Loader can load various image types and SWFs but nothing about video data, so I conclude from that that I cannot actually use the CameraUI to generate a valid MediaPromise that I can then pass to a Loader class or similar to read in the information created by the system camera and then manipulate (upload, save to applicationStorageDirectory, and/or display in one of the two video player components available in the API).
CameraRoll
I can have video entities in the iOS Camera Roll but the AS3/Air3.5 CameraRoll class won't let me view/access/reference them in any way.
Normal File I/O
All my attempts to use the Air3.5 File classes to browse to the storage location of the iOS Camera Roll have been rebuffed.
------- Questions -------
Am I correct in believing that there is a way to take video but no way to use the video that's been captured. (No way to use the resulting MediaPromise successfully).
I believe you can take video and access it using Android, but there's nothing in the documentation that says that you cannot using iOS.
Am I correct in believing that iOS sandboxes apps so that they cannot browse to video/photo storage using standard File I/O, but only through the apparently non-workable means I've tried (CameraUI & CameraRoll)
Am I wrong to think that these should be rather obvious NEEDS that one can achieve using the XCode Objective C++ etc route but the AIR Mobile Framework does not allow either because of Apple blocking functionality or because Adobe has failed to meet reasonable expectations?
One item of ironic note to convey. If I use the iOS system camera app to record a video, a thumnail of that video then appears in the Gallery/Camera Roll, and of course, I can share it or view it, or whatever... If I use AIR's CameraRoll.browseForImage(), provided I haven't used the camera to take another image, when it shows me the folder where the pictures are stored, the folder icon uses the thumbnail of the last object added... in this case, the video I took, but if I then enter the folder, the video cannot be found. It's teasing us. It knows it's there, but it is apparently forbidden fruit.
I can't answer all your questions, so this entry may not be acceptable, but I found this page while searching a solution for some the problems you described and thought that someone else may find this answer (partially) useful.
To save the movie you just took you need to open and read the data from the promise.
The iOS won't save the file anywere, so the MediaPromise.file is always null.
This is my solution to the problem:
private var camera:CameraUI;
private var dataInput:IDataInput;
public function recordVideo():void
{
// Start the camera and ask for a video
camera = new CameraUI();
camera.addEventListener(MediaEvent.COMPLETE, onCameraComplete);
camera.launch(MediaType.VIDEO);
}
private function onCameraComplete(event:MediaEvent):void
{
// event.data is a MediaPromise and MediaPromise.open() returns a IDataInput
// Let's cast it to a dispatcher and check when it's complete
dataInput = event.data.open();
var dispatcher:IEventDispatcher = IEventDispatcher(dataInput);
dispatcher.addEventListener(Event.COMPLETE, onDataInputComplete);
}
private function onDataInputComplete(event:Event):void
{
// We can do whatever we want with the data, so we'll store it in a File
var file:File = new File();
var bytes:ByteArray = new ByteArray();
var stream:FileStream = new FileStream();
// Reading the data from the opened MediaPromise
dataInput.readBytes(bytes);
stream.open(file, FileMode.WRITE);
stream.writeBytes(bytes, 0, bytes.bytesAvailable);
stream.close();
}
Also, I'm still looking for a way to put the movie in the CameraRoll

Resources