Implementing Permission Handling, Data Loading, and File Selection in a Daydream Unity Media Player
The article details how to integrate Android permission handling, directory loading, thumbnail browsing, and smooth paged scrolling into a Unity‑based Daydream media player, explaining scripts for requesting permissions, populating file selector pages, snapping to pages, and managing scroll events to enable VR file selection and playback.
Author Introduction StevenKe is a senior Android developer focusing on mobile VR development. He has systematic research on the Google Daydream SDK and frequently shares new VR technology solutions.
Media Player Overview Google released a media player source code for Daydream that can play local images and videos, as well as 360°/180° panoramic media. The project includes the following main functions:
1) Permission handling – allows users to switch from VR mode to the phone UI to grant permissions and then return to VR. 2) File selection – users can browse system folders, view media thumbnails, and select files. 3) Media playback – supports images and videos. 4) Playback controls – previous, restart, play/pause, next, and progress bar. 5) Video progress thumbnails – shows a snapshot of the video at the hovered position on the progress bar. 6) Screen adjustment – resize and reposition the player. 7) Screen lighting – simulates a realistic screen effect. 8) Idle state handling – hides the controller when idle. 9) Media format detection – automatically detects 360°, 180°, and normal 2D videos.
Permission Flow Script (PermissionFlow Controller)
public bool NeedsPermissions(){
#if UNITY_ANDROID && !UNITY_EDITOR
bool[] grantedStatuses = GvrPermissionsRequester.Instance.HasPermissionsGranted(PERMISSIONS);
foreach (bool granted in grantedStatuses) {
if (!granted) {
return true;
}
}
#endif
return false;
}
public void RequestPermissions(){
#if UNITY_ANDROID && !UNITY_EDITOR
GvrPermissionsRequester.Instance.RequestPermissions(PERMISSIONS, OnRequestPermissions);
#endif
}
private void OnRequestPermissions(GvrPermissionsRequester.PermissionStatus[] statuses){
#if UNITY_ANDROID && !UNITY_EDITOR
bool areAllPermissionsGranted = true;
foreach (GvrPermissionsRequester.PermissionStatus status in statuses) {
if (!status.Granted) {
areAllPermissionsGranted = false;
}
}
if (areAllPermissionsGranted) {
PostPermissionsGranted();
}
#endif
}
void Awake(){
if (!NeedsPermissions()) {
PostPermissionsGranted();
}
}
private void PostPermissionsGranted(){
if (postPermissionsGrantedObject != null) return;
postPermissionsGrantedObject = GameObject.Instantiate(postPermissionsGrantedPrefab);
Destroy(gameObject);
}The script checks permission status on Awake, requests missing permissions, and instantiates a prefab (named PermissionsFlow ) after the user grants them.
Data Loading and File Selection
After permissions are granted, the MediaSelector prefab is instantiated. The core logic resides in FileSelectorPageProvider , which loads directories and files, filters by allowed extensions, calculates page counts, and refreshes the UI.
public string[] AllowedExtensions {
get { return allowedExtensions; }
set { allowedExtensions = value; Refresh(); }
}
private void Refresh(){
if (workingDirectory == null) return;
try {
directories = workingDirectory.GetDirectories()
.Where(d => (d.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden)
.ToArray();
if (allowedExtensions.Length != 0) {
files = allowedExtensions.SelectMany(filter =>
workingDirectory.GetFiles("*" + filter))
.OrderBy(x => x.Name).ToArray();
} else {
files = workingDirectory.GetFiles();
}
} catch (Exception e) {
Debug.LogError(e);
directories = new DirectoryInfo[0];
files = new FileInfo[0];
}
childCount = directories.Length + files.Length;
pageCount = Mathf.CeilToInt((float)childCount / (float)MaxChildrenPerPage);
pageCount = Mathf.Max(pageCount, 1);
// Reset the paged scroll rect if needed
var scrollRect = GetComponent
();
if (scrollRect != null && scrollRect.ActivePage != null) {
scrollRect.Reset();
}
}The ProvidePage(int index) method creates a page, populates it with FileSelectorTile objects for directories and files, and wires up selection callbacks.
public RectTransform ProvidePage(int index) {
GameObject pageObj = PagePool.Borrow();
RectTransform page = pageObj.GetComponent
();
// Center the page
Vector2 middleAnchor = new Vector2(0.5f, 0.5f);
page.anchorMax = middleAnchor;
page.anchorMin = middleAnchor;
FileSelectorPage fileSelectorPage = page.GetComponent
();
int maxChildrenPerPage = MaxChildrenPerPage;
int startingChildIndex = index * maxChildrenPerPage;
int childIndex = startingChildIndex;
while (childIndex < startingChildIndex + maxChildrenPerPage && childIndex < childCount) {
FileSelectorTile tile;
if (childIndex < directories.Length) {
tile = fileSelectorPage.AddDirectoryTile(directories[childIndex]);
tile.SetToDirectory(directories[childIndex]);
tile.OnDirectorySelected += OnDirectorySelected;
} else {
int fileIdx = childIndex - directories.Length;
tile = fileSelectorPage.AddFileTile(files[fileIdx], fileIdx);
tile.OnFileSelected += OnFileSelected;
}
childIndex++;
}
return page;
}Page Navigation – SnapToPage
public void SnapToPage(int index, bool immediate = false, bool supressEvents = false) {
if (!loop && (index < 0 || index >= PageCount)) {
Debug.LogWarning("Attempting to snap to non‑existent page: " + index);
return;
}
if (immediate) {
float offset = OffsetFromIndex(index);
targetScrollOffset = offset;
ScrollOffset = offset;
} else {
activeSnapCoroutine = StartCoroutine(SnapToPageCoroutine(index));
}
if (!supressEvents) {
int currentIndex = ActiveRealIndex;
if (index < currentIndex) {
OnSwipeLeft.Invoke();
} else {
OnSwipeRight.Invoke();
}
}
}The method calculates the scroll offset for the target page and either jumps instantly or starts a coroutine for smooth animation. It also triggers swipe events unless suppressed.
Scrolling Logic – OnScrolled
private void OnScrolled() {
bool didClamp;
int newActiveIndex = IndexFromOffset(scrollOffset, out didClamp);
if (IsPageVisible(newActiveIndex)) {
ActivePage = indexToVisiblePage[newActiveIndex];
}
// Update existing pages, add missing ones, remove out‑of‑range pages
// ... (loop through visiblePages, apply effects, add pages left/right) ...
if (!indexToVisiblePage.ContainsKey(newActiveIndex)) {
AddPage(newActiveIndex, true);
}
// Add pages to the left
int nextIndex = newActiveIndex - 1;
while (true) {
if (!loop && nextIndex < 0) break;
if (IsPageVisible(nextIndex)) { nextIndex--; continue; }
if (!AddPageIfNecessary(nextIndex)) break;
nextIndex--;
}
// Add pages to the right
nextIndex = newActiveIndex + 1;
while (true) {
if (!loop && nextIndex >= pageProvider.GetNumberOfPages()) break;
if (IsPageVisible(nextIndex)) { nextIndex++; continue; }
if (!AddPageIfNecessary(nextIndex)) break;
nextIndex++;
}
}This routine updates the active page based on the current scroll offset, applies visual effects, and ensures that surrounding pages are loaded as needed.
Overall Flow
1. On start, PageScrollRect.Start() obtains the FileSelectorPageProvider and snaps to the initial page (immediate = true, supressEvents = true). 2. The provider loads directories/files, creates pages, and populates them with tiles. 3. User interaction (touchpad, controller) updates the scroll offset, triggers OnScrolled , and dynamically adds/removes pages. 4. Selecting a file invokes callbacks that eventually reach MediaSelector , which determines the media type and starts playback.
Conclusion
The Daydream media player demo demonstrates a clean separation of concerns: permission handling, data loading, UI paging, and media playback are each encapsulated in dedicated scripts. By leveraging Unity’s UI components (PagedScrollRect, GridLayoutGroup) and the Daydream SDK’s permission APIs, developers can quickly build VR file browsers and media players without reinventing the underlying logic.
iQIYI Technical Product Team
The technical product team of iQIYI
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.