Downloading images on Apple TV with Corona

Hello!

I’m posting here because it seems the Apple TV thread is a bit dead. We have an office TV, wait for it-in the office, and I built a simple Apple TV app that displays images from a web server. At a high level overview, the app downloads the images from the server and stores them into the temporary directory. Once downloaded, the app will slide through all images at 15 second intervals. On the last slide, it’ll re-download the images in case an image has changed. 

In the simulator, this works great. I can see all images and I can see the download process. However, when I put the app on Apple TV, it’s stuck on “Please wait while the slideshow initializes.”. I’ve pasted the code for slideOne.lua and there’s not much else to the app besides that. If you do want to see the full repo, it can be found at https://github.com/gamebuildingtools/slideshow-web-app. 

I already have an idea of how to better address this, but I’m really curious about why this didn’t work in the first place. Is there a setting I need to use for access to the temporary directory on Apple TV?

local composer = require( "composer" ) local scene = composer.newScene() local json = require( "json" ) -- ----------------------------------------------------------------------------------- -- Code outside of the scene event functions below will only be executed ONCE unless -- the scene is removed entirely (not recycled) via "composer.removeScene()" -- ----------------------------------------------------------------------------------- local slideImages = {} local \_CURRENTSLIDE = 0 local slideshowTime = 15 local numberOfSlides = 5 local downloadComplete = false local tmrInitialView local params = {} params.progress = true -- A function that pulls the slideshow settings and stores it as a json file function getSlideshowSettings() local function networkListener( event ) if ( event.isError ) then print( "Network error - download failed: ", event.response ) elseif ( event.phase == "began" ) then print( "Progress Phase: began" ) elseif ( event.phase == "ended" ) then print( "Completed phase" ) local filename = system.pathForFile( "slideshowSettings.json", system.TemporaryDirectory ) local decoded, pos, msg = json.decodeFile( filename ) if not decoded then print( "Decode failed at "..tostring(pos)..": "..tostring(msg) ) else print( "File successfully decoded!" ) slideshowTime = decoded.slideshowTime \* 1000 -- 1000 is the production number if(slideshowTime \< 3000) then slideshowTime = 3000; end -- Check for a minimum number numberOfSlides = decoded.numberOfSlides downloadImages() print(slideshowTime.." slideshowTime") print(numberOfSlides.." numberOfSlides") end end end -- Start the network download network.download( "http://phpstack-36122-277073.cloudwaysapps.com/slideshowSettings.json", "GET", networkListener, params, "slideshowSettings.json", system.TemporaryDirectory ) end -- ----------------------------------------------------------------------------------- -- Scene event functions -- ----------------------------------------------------------------------------------- function downloadImages() -- Do nothing, this is an empty listener local function emptyListener() end local function downloadListener(event) if ( event.isError ) then print( "Network error - download failed: ", event.response ) elseif ( event.phase == "began" ) then print( "1 Progress Phase: began" ) elseif ( event.phase == "ended" ) then print('2 Download complete') timer.performWithDelay(8000, function() downloadComplete = true print("\*\*\*\*\*\*") end, 1) end end for i=0,numberOfSlides-1 do if(i \< numberOfSlides-1) then network.download( "http://phpstack-36122-277073.cloudwaysapps.com/images/"..i..".jpg", "GET", emptyListener, i..".jpg", system.TemporaryDirectory ) elseif(i == numberOfSlides-1) then network.download( "http://phpstack-36122-277073.cloudwaysapps.com/images/"..i..".jpg", "GET", downloadListener, i..".jpg", system.TemporaryDirectory ) end end \_CURRENTSLIDE = 1 end -- create() function scene:create( event ) local sceneGroup = self.view getSlideshowSettings() -- Call the function to get things moving end -- show() function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- Code here runs when the scene is still off screen (but is about to come on screen) elseif ( phase == "did" ) then -- Code here runs when the scene is entirely on screen, nextSlide will transition from one slide to the next. On completion, the slides will be re-downloaded to grab any updated images local function nextSlide() local prevSlide = \_CURRENTSLIDE-1 if( prevSlide \<= 0) then prevSlide = 0; end transition.to(slideImages[prevSlide], {x = \_L-1000, time = 1000, delay=10, onComplete=function() slideImages[prevSlide].x = \_R + 1000 \_CURRENTSLIDE = \_CURRENTSLIDE + 1 end}) if(\_CURRENTSLIDE \>= numberOfSlides) then \_CURRENTSLIDE = 0 downloadImages() getSlideshowSettings() end transition.to(slideImages[\_CURRENTSLIDE], {x = \_CX, time = 1000}) print(\_CURRENTSLIDE) end -- When the download is complete, initialize the images into an array and position all but the first off screen. local function loadInitialView() if(downloadComplete == true) then for i=0,numberOfSlides-1 do slideImages[i] = display.newImageRect(sceneGroup, i..".jpg", system.TemporaryDirectory, 1920, 1080) slideImages[i].x = \_R + 1000 slideImages[i].y = \_CY end timer.cancel(tmrInitialView) slideImages[0].x = \_CX slideImages[0].y = \_CY timer.performWithDelay(slideshowTime, nextSlide, 0) end end -- Start a time that waits for the download to complete. tmrInitialView = timer.performWithDelay(1000, loadInitialView, 0) local loadingText = display.newText(sceneGroup, "Please wait while the slideshow initializes.", 0, 0, native.systemFont, 32) loadingText.x = \_CX loadingText.y = \_CY end end -- ----------------------------------------------------------------------------------- -- Scene event function listeners -- ----------------------------------------------------------------------------------- scene:addEventListener( "create", scene ) scene:addEventListener( "show", scene ) -- ----------------------------------------------------------------------------------- return scene

Have you considered using system.CachesDirectory instead?  

Nope! Are there any special considerations when using system.CachesDirectory? The documentation page at docs.coronalabs.com/api/library/system/CachesDirectory.html doesn’t have a lot. A couple of questions come to mind. 

Can I overwrite images in the caches directory?

Does this operate similar to how a website caching system works, i.e. varnish or nginx?

Do I need to do anything to get access to this directory on Apple TV?

Thanks Rob!

Apple added the Caches directory a while back for the sole purpose of storying files you can download from the Internet. They don’t get backed up but they may be retained a bit longer than files in the tmp directory.

It’s more like how web browsers cache files they have downloaded. 

Simply change system.TemporaryDirectory to system.CachesDirectory.

If the file has the same name and the OS thinks you have the newest version then it likely won’t download it again. If you need to force a fresh download you need to change the URL somehow. Some people will tack a HTTP GET parameter on the end of the URL like:

https://myserver.com/myfile.png?cb=” … tostring(os.time()"

That way the system thinks its a different request and it will download file after stripping of the GET key/value pairs. “cb” stands for “cache buster” but the name doesn’t matter. It’s just what I use to let me know the parameter is there to bust the cache.

Rob

Cache buster? I’ll need to use that term in other projects! Cache is probably one of the most frustrating things when multiple levels of caching (I’m looking at you CloudWays) is implemented.

Hey Rob!

I’ve got another question. I’m still not able to get images to load on the app for the Apple TV. The image exists on the server, this code works in the simulator, but I’m not able to get this to load on the Apple TV. Can you help me with this or point me in the right direction?

local function networkListener( event ) if ( event.isError ) then print ( "Network error - download failed" ) else event.target.alpha = 0 event.target.x = \_CX event.target.y = \_CY transition.to( event.target, { alpha = 1.0 } ) end print ( "event.response.fullPath: ", event.response.fullPath ) print ( "event.response.filename: ", event.response.filename ) print ( "event.response.baseDirectory: ", event.response.baseDirectory ) end display.loadRemoteImage( "http://phpstack-36122-277073.cloudwaysapps.com/images/1.jpg", "GET", networkListener, "coronalogogrey.png", system.TemporaryDirectory, 50, 50 )

Are you getting any errors?  

Are you using your mac and having it install to the AppleTV?

Do you have an ATS exception for the website (You’re using http: instead of https:)… Can you post your build.settings?

I see you have prints in your networkListener, what’s printing out?

Rob

Have you considered using system.CachesDirectory instead?  

Nope! Are there any special considerations when using system.CachesDirectory? The documentation page at docs.coronalabs.com/api/library/system/CachesDirectory.html doesn’t have a lot. A couple of questions come to mind. 

Can I overwrite images in the caches directory?

Does this operate similar to how a website caching system works, i.e. varnish or nginx?

Do I need to do anything to get access to this directory on Apple TV?

Thanks Rob!

Apple added the Caches directory a while back for the sole purpose of storying files you can download from the Internet. They don’t get backed up but they may be retained a bit longer than files in the tmp directory.

It’s more like how web browsers cache files they have downloaded. 

Simply change system.TemporaryDirectory to system.CachesDirectory.

If the file has the same name and the OS thinks you have the newest version then it likely won’t download it again. If you need to force a fresh download you need to change the URL somehow. Some people will tack a HTTP GET parameter on the end of the URL like:

https://myserver.com/myfile.png?cb=” … tostring(os.time()"

That way the system thinks its a different request and it will download file after stripping of the GET key/value pairs. “cb” stands for “cache buster” but the name doesn’t matter. It’s just what I use to let me know the parameter is there to bust the cache.

Rob

Cache buster? I’ll need to use that term in other projects! Cache is probably one of the most frustrating things when multiple levels of caching (I’m looking at you CloudWays) is implemented.

Hey Rob!

I’ve got another question. I’m still not able to get images to load on the app for the Apple TV. The image exists on the server, this code works in the simulator, but I’m not able to get this to load on the Apple TV. Can you help me with this or point me in the right direction?

local function networkListener( event ) if ( event.isError ) then print ( "Network error - download failed" ) else event.target.alpha = 0 event.target.x = \_CX event.target.y = \_CY transition.to( event.target, { alpha = 1.0 } ) end print ( "event.response.fullPath: ", event.response.fullPath ) print ( "event.response.filename: ", event.response.filename ) print ( "event.response.baseDirectory: ", event.response.baseDirectory ) end display.loadRemoteImage( "http://phpstack-36122-277073.cloudwaysapps.com/images/1.jpg", "GET", networkListener, "coronalogogrey.png", system.TemporaryDirectory, 50, 50 )

Are you getting any errors?  

Are you using your mac and having it install to the AppleTV?

Do you have an ATS exception for the website (You’re using http: instead of https:)… Can you post your build.settings?

I see you have prints in your networkListener, what’s printing out?

Rob