Skip to content
This repository was archived by the owner on Mar 12, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/build-android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Build Questopia APK

on:
workflow_dispatch: # lets you click 'Run workflow' by hand
push: # builds on every commit to master/main
branches: [ master, main, feature/video-support ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'

- name: Set up Android SDK (cmdline-tools, platform-tools, licenses)
uses: android-actions/setup-android@v3
# This action installs the commandline tools and accepts SDK licenses for us.

- name: Install required SDK packages
run: |
sdkmanager --sdk_root="$ANDROID_SDK_ROOT" \
"platform-tools" \
"platforms;android-34" \
"build-tools;34.0.0" \
"cmake;3.22.1" \
"ndk;27.2.12479018"

- name: Make Gradle wrapper executable
run: chmod +x ./gradlew

- name: Build debug APK
run: ./gradlew assembleDebug --stacktrace

- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: questopia-debug-apk
path: app/build/outputs/apk/debug/*.apk
6 changes: 5 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ android {

dependencies {
coreLibraryDesugaring(libs.desugar.jdk.libs)

// Media3 / ExoPlayer
implementation "androidx.media3:media3-exoplayer:1.4.1"
implementation "androidx.media3:media3-ui:1.4.1"

implementation libs.appcompat
implementation libs.constraintlayout
Expand Down Expand Up @@ -120,4 +124,4 @@ dependencies {
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
}
}
13 changes: 12 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Android 13+ granular media access -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Legacy (< Android 13) fallback permission, harmless on 13+ -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />


<queries android:exported="false">
<intent>
Expand All @@ -30,6 +36,11 @@
android:dataExtractionRules="@xml/data_extraction_rules"
tools:targetApi="s">

<activity
android:name="org.qp.android.ui.video.VideoPlayerActivity"
android:exported="false"
android:theme="@style/Theme.Material3.DayNight.NoActionBar" />

<activity
android:name=".ui.stock.StockActivity"
android:exported="true"
Expand Down Expand Up @@ -71,4 +82,4 @@
</provider>
</application>

</manifest>
</manifest>
16 changes: 16 additions & 0 deletions app/src/main/app/src/main/res/layout/activity_video_player.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.media3.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:show_buffering="when_playing"
app:use_controller="true"
app:controller_show_timeout_ms="3500"/>

</androidx.constraintlayout.widget.ConstraintLayout>
29 changes: 28 additions & 1 deletion app/src/main/java/org/qp/android/model/lib/LibProxyImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.qp.android.helpers.utils.ThreadUtil.throwIfNotMainThread;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
Expand All @@ -35,6 +36,7 @@
import org.qp.android.model.service.HtmlProcessor;
import org.qp.android.ui.game.GameInterface;


import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -502,7 +504,32 @@ public void onShowMessage(String text) {
@Override
public void onPlayFile(String file, int volume) {
if (!isNotEmptyOrBlank(file)) return;


// Convert to URI so we can check extension
Uri uri = Uri.parse(file);

// Extract extension safely
String last = uri.getLastPathSegment();
String ext = last != null && last.contains(".")
? last.substring(last.lastIndexOf('.') + 1).toLowerCase()
: "";

// Check if this file is a video
boolean isVideo = java.util.Arrays.asList(
"mp4", "m4v", "mov", "webm", "3gp", "3gpp", "mkv"
).contains(ext);

if (isVideo) {
// If video: launch custom VideoPlayerActivity
Intent i = new Intent(context, org.qp.android.ui.video.VideoPlayerActivity.class);
i.putExtra("videoUri", uri.toString());
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // required if not from an Activity
context.startActivity(i);

return; // stop here so we don’t send video to AudioPlayer
}

// Otherwise: handle as audio normally
getAudioPlayer().playFile(file, volume);
}

Expand Down
37 changes: 37 additions & 0 deletions app/src/main/java/org/qp/android/ui/video/VideoPlayerActivity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.qp.android.ui.video;

import android.net.Uri;
import android.os.Bundle;
import android.widget.MediaController;
import android.widget.VideoView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class VideoPlayerActivity extends AppCompatActivity {

public static final String EXTRA_VIDEO_URI = "video_uri";

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Create VideoView programmatically (you can also use a layout file if you prefer)
VideoView videoView = new VideoView(this);
setContentView(videoView);

// Get video URI from intent extras
String uriString = getIntent().getStringExtra(EXTRA_VIDEO_URI);
if (uriString != null) {
Uri videoUri = Uri.parse(uriString);
videoView.setVideoURI(videoUri);

// Optional: add media controls (play/pause/seek bar)
MediaController mediaController = new MediaController(this);
mediaController.setAnchorView(videoView);
videoView.setMediaController(mediaController);

videoView.start();
}
}
}