Large files — I’m talking really large, say 8 gb — can be frustrating to download. How much fun it is to stare at the spinner icon or progress bar for more than a few seconds, let alone hours? When we first came up with an interactive storytelling tool called ViewPoint we butted up against this problem. But we found a solution that can work for you in a variety of non-ViewPoint environments.
ViewPoint, which contains a built-in browser and video player, is essentially a presentation tool for custom touchscreen “experiences” that are shown on a variety of hardware, from large-format kiosks down to tablets. ViewPoint presentations are written in Qt/QML and run on any platform that supports touchscreens, such as Windows, Linux, MacOS, Android, RaspberryPi and iOS/iPadOS.
Qt/QML works remarkably well on all thеse platforms. Yes, some platform-specific tweaks are needed, but 98.7 % of ViewPoint’s code is platform independent. (No, I did not do lines-of-code calculations; this is my best guesstimate.)
Each presentation or experience typically contains a lot of collateral, including videos, animations, PDFs etc. That means they can quickly grow very large. After all, it’s so easy to add another video or another animation or another set of of hi-res images, so why not? And it’s also easy to revise these presentations.
But, that’s where the troubles begin. A new version of a presentation means data on each device running ViewPoint must be updated. Say ViewPoint is being used by a company’s remote sales force. Imagine hundreds of iPads requiring updates within a short period of time. It can’t be done one by one.
This scenario can be a problem well beyond ViewPoint. Pretty much anyone who needs to download very large files will encounter major challenges. Fortunately, while working with ViewPoint, we devised an effective method. It starts with ensuring Qt and QML run seamlessly on iOS.
Here’s a look at how we solved the large-download problem for ViewPoint — insight transferable to your own download issues.
Running Qt/QML on iOS
It’s not that difficult to make them work together. Qt and QML are both C++-based, which iOS fully supports. iOS was written in Objective-C that can be compiled as Objective-C++. Technically, this is done by simply changing the file extension from myfile.m as in Objective-C to myfile.mm as in Objective-C++. The rest is handled by the toolchain.
You can use C++ and Objective-C++ code in the project with no problem with minimum glue code. The issue is that Objective-C is considered an old and cumbersome language, and is in fact quite a polarizing issue. Some developers like Objective-C; others hate it with a passion. That’s one reason that in 2014 Apple introduced the new, modern language Swift. These days, application development is mostly done on Swift — great news if you write pure iOS code. But not so great for Qt/QML developers who want their code to work on iOS.
I’ll address this point in a moment.
Getting to the Large Files
Obviously, it’s not a good idea to block a user from using the app during download, which may take hours. It’s equally bad to force user to keep the app open during this time. So how do you solve?
The first thing that comes to mind is to build a separate download manager that connects to the server and keeps the presentations up-to-date without the user being asked to do anything at all. At some moment, a new presentation just appears as if by magic. This approach works nicely on Linux and Windows — even on MacOS.
But not on iOS.
Each app on iOS is sandboxed for security reasons. Yes, there’s a way around it. You can create a group of apps to share the same sandboxed data. But then only one app can be active, and there is no way to launch one app from another. You can ask the user to start both apps at the same time, and create a communication channel between both, but this seems too complicated to be the right solution. Besides, we’re not the only ones who need this functionality.
Background Download on iOS
So what’s the solution? Background download on iOS. This solution works quite well — but it wasn’t obvious. iOS is rather strict about what is allowed and what isn’t. You’d have to look for this thing specifically. But that’s challenging because while Apple produces very good documentation for basic tasks, the amount of available documentation decreases for more complex tasks. (It makes sense in general; only a relative few developers may need to use advanced features and they are usually more experienced folks.)
Thankfully, Swift documentation describes protocols on both Swift and Objective-C. The examples are in Swift only (here’s an example) but it provides enough information that we’re happy.
Background download resolves our issue!
Here’s how it works. When a user requests a background download, iOS spawns a separate process that does only this — downloads the file in the background. This process sends status notifications to the parent process. The parent process must conform to NSURLSessionTaskDelegate protocol to be able to receive notifications.
The parent process can go to background or it can stay active so the device can go to sleep without breaking the download. However, if the user decides to terminate the running app, the download is cancelled and can’t be resumed.
So that tells you what to do. Now, here’s how to do it. Usually there are two things to look at: documentation and examples, hopefully in reverse order — examples of what it does, and then learning APIs to see how it is actually implemented. Since I couldn’t find Objective-C examples in Apple documentation, I’ll share a Swift example. This example is not a big task, but you need to be comfortable with Swift to appreciate it.
Here is the plan: Swift example followed by Objective-C example. Then Objective-C methods will be copied into Qt/QML-based codebase, and hopefully we’ll be done.
There are few things to pay attention with the Swift app:
We want to be able to use the app while downloading data
We want the download to continue while we use another app
We want the download to continue while we put the device aside and it goes to sleep
We want the data to be of substantial size to emulate real-world usage
We want it to be as simple an app as possible
A video player app satisfies all these conditions. They’re simple to implement, and it’s easy to see that one video is playing while the other is being downloaded. Plus, the video files can be as big as we need them to be.
We have to configure the download task, and we have to implement a number of delegate functions that will be called at different stages of the process — download update, successful completion, or download error. None of these are overly complicated.
Line 63 - We use hard-coded filename in this example.
Line 66 - URLSession configured as background session.
Line 67 - The identifier allows us to distinguish between multiple download sessions. For instance, say we want to download several files simultaneously. Our delegate method should be able to know which one has been completed.
Line 70 - self in Swift and Objective-C is analog to this in C++. We are saying to URLSession that this class is the delegate, hence invoking our implementation of delegate methods.
Download Task Delegate Methods
There are two delegate methods for URLSessionDownloadDelegate: download progress and download completion. Documentation specifies a few more, but these two are essential for our purposes.
Line 137 - URLSessionDownloadDelegate. There are several protocols defined for different parts of the process, like NSURLSessionTaskDelegate, and NSURLSessionDelegate. You may want to implement methods for these protocols, as well.
Line 153 - Move file to the permanent location. Background download keeps the file in a temporary location, and we have to move it to the permanent location.
Line 168 - Ask system to update UI at the proper moment. Trying to update the UI directly may cause a crash.
Got all that? Good. Now we have a working example on Swift. No, it’s not refined but it gives us a really good starting point.
One caveat: we don’t check the validity of the received data. Say, there is no YourVideo.mov on the server. The server will respond with a nicely formed html page saying so — error 404. Our example will happily copy this html file as YourVideo.mov so just be aware.
Objective-C code example
This task is a matter of mapping Swift-based APIs to Objective-C based APIs. Easy enough once you have a working example.
Swift requires fewer lines and is easier to read, but we still have to use Objective-C with Qt.
The integration is pretty straightforward. We have the core functionality implemented in the example. We need to supply parameters, such as source URL and destination URL. We need to be able to display current status. We can implement Pause/Resume too.
We use singleton to bridge between Qt and Objective-C parts. This singleton object sends and receives messages to/from our iOS-specific module, and we can do regular signal/slot communication between the singleton and the rest of the application.
While everything works well in our example, the process can still be fine tuned by addressing:
We experimented with all of these. Now it is your turn! Hopefully, I’ve provided enough information for you to do a little experimenting on your own. Have fun! And if you enjoyed this blog, check out some of my other posts here.