Sunday, November 28, 2010

[How To] Compiling VLC in XCode for iPad simulator and device

[EDIT Feb 2012]:
It has been a pretty long time since I put this post together and a lot has changed in VLC, FFMPEG and iOS since then. Whilst I hope that there is still useful information here I am not sure that this guide should be considered up to date and correct anymore. Maybe one day I can find time to update it but I am just too busy with other projects for it to be top of my list.
Do keep leaving comments if you have corrections or new info and I will try and add them in.


[NOTE]:
Sorry but I am not able to distribute pre-built binaries for any part of this app so please don't waste time asking me to send you a compiled version, it probably wouldn't work anyways (code signing etc.)

I have been messing with the VLC iOS source recently to see if I could hack it up to work with the new airplay protocols for Apple TV. Sadly any airplay hook up is not going to be straight forward as the VLC player is not using a MPMoviePlayerController instance or subclass to display the movie. It's not clear yet if I can hijack the video stream from the custom view and pipe it over the hard way but I will keep hacking in spare moments.

[UPDATE]:
Now that VLC has been pulled from the AppStore I am expecting that there will be many more people interested in compiling VLC themselves. I will hopefully find some time soon to do some more hacking on VLC myself soon and will update this guide if I find bits of the process change.

Actually getting the source to compile was a bit of a mission and looking at the videolan forums there are a few others out there struggling to compile the sources too so here is how I managed it. There are quite a few steps so I have split it up into the three main tasks.

Build the Aggregate libraries.
  1. Install git if you don't have it already. You will need this to grab the latest source and for the build script to complete.
  2. Open up a terminal window and cd to the directory you want to download and build in.
  3. clone the MobileVLC.git repo.
     git clone git://git.videolan.org/MobileVLC.git  
    
  4. Open up buildMobileVLC.sh in your favourite editor.
  5. I had a problem with the build script failing trying to remove symbolic links that didn't exist. I fixed this by editing line 121 to wrap a remove in a conditional.
    From:
     rm External/MobileVLCKit  
    
    To:
     if [ -e External/MobileVLCKit ]; then  
     rm External/MobileVLCKit  
     fi  
    
  6. If you have XCode setup to use a custom build location (for instance if you use a shared location to leverage project includes in your other projects) then the easiest way to get this working is to go to preferences and set the option back to the default (Place build products in project directory). If you don't do this then you will need to edit the buildMobileVLC.sh lines 117 and 118 so that the products can be found.
  7. In theory the buildMobileVLC.sh script should take a flag to set the SDK you want to use. However in practice I found that it didn't so edited all occurences of 3.2 to be 4.2.
  8. At the time of writing a recent commit causes the AggregateStaticPlugins.sh script phase of MobileVLCKit to fail.  Check that ImportedSources/vlc/projects/macosx/framework/MobileVLCKit/AggregateStaticPlugins.sh does not have plugins+="access/access_mmap ". If it does then just delete this line. ** This has been resolved in the latest revision.
  9. In terminal cd into the MobileVLC directory and execute buildMobileVLC.sh passing the -s flag for simulator, then run it again without the flag to build for device. This is just so that later you can switch between simulator and device in XCode without any problem.
  10. The script will take a little while to run, especially first time out as it needs to bring in 2 repos from git.videolan.org and compile a metric ass ton of dependant libraries. There are a shocking amount of warnings and errors reported by the build of the libraries, no wonder building this thing is so brittle. Once its finished you are looking to see the magic words "Build complete" and you should be OK. 
Configure XCode to get simulator working
  1. Next open up the MobileVLC.xcodeproj. We will get it running from here in the simulator first.
  2. Select the project root and open up the inspector (get info). On the General tab set the "Base SDK for all configurations" to be the SDK you are using (in this case it was 4.2).
  3. Close the inspector and toggle the target select dropdown to debug then back to release so that the "missing base SDK" warning disappears.
  4. Now you can set the target hardware to Simulator and the active executable to iPad (although this guide should work for iPhone too).
  5. You can if you like hit build and run but the VLC app will crash as soon as it launches. This is because there is a bad path to the MediaLibrary data model for core data.
    Expand the External/MediaLibraryKit group and select the MediaLibrary.xcdatamodel (it should be highlighted red showing there is a problem) and open inspector.
  6. Hit choose to find the correct the path. Project relative it is ImportedSources/MediaLibraryKit/MediaLibrary.xcdatamodel.
  7. Finally you should be able to hit build and run and see the VLC app open up in the simulator.
Configure XCode to get it on a device
  1. I am going to assume you already have a developer certificate and an application agnostic Team Provisioning Profile. If not then this next bit is of no use to you anyway.
  2. Select the MobileVLC target and open the inspector. 
  3. On the build tab set the code signing entity to use your developer identity.
  4. Change the target to device in the target select dropdown, plug in your iPad and build and run.
  5. If the build fails at this point with something like:
     file was built for unsupported file format which is not the architecture being linked (armv7)  
     Undefined symbols:  
      "_OBJC_CLASS_$_VLCMediaPlayer", referenced from:  
        objc-class-ref-to-VLCMediaPlayer in MVLCMovieViewController.o  
      "_OBJC_CLASS_$_VLCMedia", referenced from:  
        objc-class-ref-to-VLCMedia in MVLCMovieViewController.o  
      "_OBJC_CLASS_$_VLCTime", referenced from:  
        objc-class-ref-to-VLCTime in MVLCMovieGridViewCell.o  
      "_OBJC_CLASS_$_MLMediaLibrary", referenced from:  
        objc-class-ref-to-MLMediaLibrary in MobileVLCAppDelegate.o  
      "_OBJC_CLASS_$_MLFile", referenced from:  
        objc-class-ref-to-MLFile in MVLCMovieListViewController.o  
        l_OBJC_$_CATEGORY_MLFile_$_HD in MLFile+HD.o  
     ld: symbol(s) not found  
     collect2: ld returned 1 exit status  
    
    Then go back to terminal and run buildMobileVLC.sh again with no flags.
  6. You should now be seeing the VLC app running on the iPad.

Friday, November 19, 2010

Graffiti on office wall

Problem with @dynamic property declarations for core data managed objects

I have just had the most frustrating debug session I can remember due, I believe, to the (recommended by Apple) use of the @dynamic declaration for NSManagedObject properties.

I have an object that is defined like this:

 @interface SomeObject : NSManagedObject {  
 }  
 @property (nonatomic, retain) NSNumber * isRead;  
 @end  
 ...  
 @implementation SomeObject  
 @Dynamic isRead;  
 ...  
 @end  

Apple tells us to use the @Dynamic declaration instead of @synthesize as a speed optimisation. What this means however is that the return type of isRead can not be relied upon to be an NSNumber, nor can it be relied upon to be the same every time!

In this particular situation the core data was being refreshed from a JSON web service if the device had an internet connection or read from disk if it was offline. The behaviour of the application depends upon the YES/NO value of isRead.

As an aside here the reason that isRead is NSNumber rather than BOOL is because when the data is read in from the JSON service it is converted to an NSDictionary and the true/false string of the web service ends up coming out as NSNumber 1/0 because only objects can be added to a dictionary and BOOL is a primitive.

SO...
when offline

2010-11-19 15:27:21.732 MyApp[1086:207] SomeObject isRead is 0
2010-11-19 15:27:21.742 MyApp[1086:207] SomeObject isRead is of type _PFCachedNumber

when Online

2010-11-19 15:30:41.415 MyApp[1111:207] SomeObject isRead is 0
2010-11-19 15:30:41.424 MyApp[1111:207] SomeObject isRead is of type NSCFBoolean

This caused a monstrous bug because we were trying to compare an NSNumber like this

 if(SomeObject.isRead == [NSNumber numberWithBool:NO]){  
  //do stuff  
 }  

Which would work if the device was online but not if there was no network. try tracking that one down! To fix it I had to reverse the polarity on the test.

 if(![SomeObject.isRead boolValue]){  
  //do stuff  
 }  

Which in all honesty is probably a better way of writing the test.

Bitmasks, how the fuck do they work?

This was an actual question I was asked when I suggested "Use a bitmask" as a solution to a messy option list problem.

The Scenario:
The web service (or whatever) you are communicating with has a load of options that can be toggled on and off for a particular resource/operation/whatever. The somewhat basic design comes back as specifying in the JSON (I'm going to stop adding the whatever now mmkay) that you set these like so...

 {  
  "thing" : {  
    "option1" : "true",  
    "option2" : "false",  
    "option3: : "true",  
 ....  
  }  
 }  

Which looks fine enough except that when there is a lot of these options its both a pain setting them up before you send and its not really that efficient data transmission wise.

Instead think about using a bitmask. First there is the spec. (This is using C/C++/Obj C)

 typedef enum {  
  Option1 = 0,  
  Option2 = 1 << 0,  
  Option3 = 1 << 1,  
  Option4 = 1 << 2  
 } OptionTypes  

If you are using some other language you could have a hash/dictionary/associative array etc. where we are increasing the count in base 2 such as...

 OptionTypes = {  
  "Option1" = 1,  
  "Option2" = 2,  
  "Option3" = 4,
  "Option4" = 8,  
 ...  
 }  

Then to define the set of options in use you just use a BITWISE OR and test for options in use with a BITWISE AND.

 // set  
 myOptionSet = (Option1 | Option2 | Option4);  

 // test  
 if(myOptionSet & Option1) doOptionOneThing;  
 if(myOptionSet & Option2) doOptionTwoThing;  
 ...  

Which can be sent to the web service with a single integer value.

 // assuming 1,2 and 4
 {  
  "thing" : {  
    "options" : 11,  
 ....  
  }  
 }  

Simples.

Up at the crack of dawn

Monday, November 15, 2010

System restore "disk" for MacBook Air

Sunday, November 14, 2010

First Apple TV app - Hello World

What else was it going to be? I still don't have a clue what the published headers all do but at least I can get something to compile.

Wednesday, November 10, 2010

Getting the Apple TV 2g into DFU for jailbreak

I've been having some fun the last couple of nights poking around inside the new Apple TV. Out of the box it doesn't really appeal to me at all, I am just not interested in having a locked in iTunes rental box. Once jailbroken though with access to all that lovely iOS framework underneath there are definitely a few uses I can find for a £100 HD N class wifi media streamer with a palm sized form factor.

Anyways I had a whole heap of trouble actually getting the bastard thing broken in the first place because no matter what I tried I just couldn't get it into DFU (Device Firmware Upgrade) mode to flash the modified firmware onto it. I tried using the Pwnage tool with just the USB plugged in as all the tutorials suggest, I tried the manual reboot method (holding menu+down then menu+play) but nothing worked.

From all the trawling on the internets trying to work out what was going on I saw that I was not the only one having trouble. In the end I didn't find the method that worked for me online so am posting this in case it helps any other poor sods out there in the same spot.

  1. Chances are that in trying to get it into DFU and failing the device is in restore mode. Plug the ATV into the TV and power and if the screen is showing the "connect to iTunes" graphic then it's in restore mode. If so then get it back into a fresh state and connect to your computer using only the USB cable and let iTunes restore it.
    - Note that this is still when the only official FW out is the original 4.1 so doing a restore and update is no big deal. You did of course save your SHSH blobs before you started all this right?
  2. Now that your ATV is back to factory settings unplug the USB and plug the power back in. No need to connect it to the TV. Fire up the Pwnage tool (I am assuming here that you have already built the custom .ipsw file but if not now is a good time to do so) select the ATV device and hit the DFU button.
  3. With the power cable plugged into the ATV now plug the USB into the computer as directed by the Pwnage tool.
  4. Pwnage tool now tells you to disconnect the power. Do as it says. 
  5. When instructed hold down the menu and play buttons together for 7 seconds and release when instructed.
  6. Hold your breath. (not sure this is mandatory but it worked for me).
  7. Now you should be in DFU and can go ahead with the option-restore in iTunes to load your custom firmware.

I think the key step here is the initial connection with the power cable attached. Unplugging the power cable lets the device reboot at just the right time for the timed DFU steps in the Pwnage tool to work "just so".

Once you have the new firmware installed plug the ATV back into the TV and power and go configure it for your network. It will not look any different to normal at this point. Once you have a network connection you can ssh into the box as root (default password "alpine" change it as soon as you are in) and get to the hacking proper.
Good luck to you, hope this helps.

Wednesday, November 03, 2010

Automating build versioning in XCode for iOS with SVN and Dropbox

I am working on an iOS project at the minute that is heavily dependant on a test team actually getting the latest version of the app onto a variety of devices and tapping away to verify the latest feature*. At certain times in the sprint as defects come in are fixed quickly and a new version put out it was becoming very difficult to keep track of the builds and who had what.

To make matters worse the way a tester would get a new build was for them to walk up to a developer and ask for the latest stable version. The developer would then need to stop what he was doing, grab the relevant version from svn, plug the test device into their machine and hit build. This was horrible for workflow and productivity.

This week sees the first real run with my automagical build system and so far its working great. Basically it uses the "build and archive" function to share an installable build to a dedicated build station running iTunes. All the devices are synced to this version of iTunes and the binary gets to the machine by a network folder - we happen to use Dropbox for this.

This works OK but there are a couple of gotchas. In order for iTunes to recognise the incoming build as an update the version number must have changed. This meant that the developers needed to keep the minor version number rolling along and we resorted to naming the binary with a number including the SVN revision and the date and time. I didn't like this much so came up with a better way of numbering the versions.

Modifying a script I found here I added a bit that pulls the last subversion revision from svnversion (including the Modified flag if appropriate) and creates a build version number like M.M.M.svn.m for example. 2.0.1.1923M.3 is the third build from a modified SVN revision 1923 of release 2.0.1.

Here's the script:

 #!/bin/bash  
 PROJECTMAIN=$(pwd)  
 PROJECT_NAME=$(basename "${PROJECTMAIN}")  
 echo -e "starting build number script for ${PROJECTMAIN}"  
   
 # find the plist file  
 if [ -f "${PROJECTMAIN}/Resources/${PROJECT_NAME}-Info.plist" ]  
 then  
   buildPlist="${PROJECTMAIN}/Resources/${PROJECT_NAME}-Info.plist"  
 elif [ -f "${PROJECTMAIN}/resources/${PROJECT_NAME}-Info.plist" ]  
 then  
   buildPlist="${PROJECTMAIN}/resources/${PROJECT_NAME}-Info.plist"  
 elif [ -f "${PROJECTMAIN}/${PROJECT_NAME}-Info.plist" ]  
 then  
   buildPlist="${PROJECTMAIN}/${PROJECT_NAME}-Info.plist"  
 else  
   echo -e "Can't find the plist: ${PROJECT_NAME}-Info.plist"  
   exit 1  
 fi  
   
 # try and get the build version from the plist  
 buildVersion=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${buildPlist}" 2>/dev/null)  
 if [ "${buildVersion}" = "" ]  
 then  
   echo -e "\"${buildPlist}\" does not contain key: \"CFBundleVersion\""  
   exit 1  
 fi  
   
 echo -e "current version number == ${buildVersion}"  
   
 # get the subversion revision  
 svnVersion=$(svnversion . | perl -p -e "s/([\d]*:)([\d+[M|S]*).*/\$2/")  
 echo -e "svn version = ${svnVersion}"  
   
 # construct a new build number  
 IFS='.'  
 set $buildVersion   
 MAJOR_VERSION="${1}.${2}.${3}"  
 MINOR_VERSION="${5}"  
   
 if [ ${4} != ${svnVersion} ]  
 then  
 buildNumber=0  
 else  
 buildNumber=$(($MINOR_VERSION + 1))  
 fi  
   
 buildNewVersion="${MAJOR_VERSION}.${svnVersion}.${buildNumber}"  
 echo -e "new version number: ${buildNewVersion}"  
   
 # write it back to the plist  
 /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${buildNewVersion}" "${buildPlist}"  

Just place it in your project directory and tell XCode to run it as a the first phase of the build by adding a new "Run Script" build phase. Then getInfo on the new phase and set shell: /bin/bash and script: ./path/to/script

Now when the build and archive runs I can grab the build version from the results window and add it to the resolve comment in Jira so the tester knows which build to grab from the dropbox. All they need to do is go to the build station, click the build in the dock stack (hooray for stacks and instant context opens) which will replace the version in the iTunes library with that build. Then they just plug in the device and it updates.

Next step will be to subscribe all the devices to a twitter feed using some iOS client that does push notifications on @mentions so that I can tell a device to get a new build from the station.

    Tea made by robot. (kind of)

    I have a new robot friend.


    Hey little buddy lemme look in your belly, yummy camomile flowers.



    Nooo! what are you doing? halp!

    slurp!