feat(player): software orientation (portrait + flipped) on both players (1.7.12)

The dashboard exposes landscape / portrait / landscape-flipped / portrait-flipped
and the README promises rotation, but neither player ever read the device's
orientation field - it was hardcoded landscape. Reported by a customer testing
Firestick + Samsung signage.

Rotate the CONTENT in software, not the panel: Fire TV / Android TV / Tizen are
fixed-landscape and ignore setRequestedOrientation (can't physically rotate).
- Android (MainActivity): applyOrientation() resizes rootView to the rotated
  dimensions, recenters, and rotates 0/90/180/270. rootView is the shared
  container for single-zone AND multi-zone, so both are covered. Driven from the
  playlist-update payload.
- Tizen (app.js): CSS transform on the stage (rotate + swapped 100vh/100vw),
  same four values, from the playlist payload.

Verified on an Android 16 emulator: device set to portrait -> 'Applied
orientation: portrait (rotation=90, swap=true)' and the video renders rotated.
This commit is contained in:
ScreenTinker 2026-06-09 21:43:08 -05:00
parent f98bb57ab9
commit dfc8a4e358
4 changed files with 57 additions and 3 deletions

View file

@ -1 +1 @@
1.7.11
1.7.12

View file

@ -11,8 +11,8 @@ android {
applicationId = "com.remotedisplay.player"
minSdk = 26
targetSdk = 34
versionCode = 14
versionName = "1.7.11"
versionCode = 15
versionName = "1.7.12"
}
signingConfigs {

View file

@ -52,6 +52,7 @@ class MainActivity : AppCompatActivity() {
private lateinit var statusOverlay: View
private lateinit var statusText: TextView
private lateinit var rootView: View
private var currentOrientation: String? = null
private val handler = Handler(Looper.getMainLooper())
private var remoteStreaming = false
@ -198,9 +199,40 @@ class MainActivity : AppCompatActivity() {
}
// Rotate the whole stage in software so portrait / flipped signage works even on
// fixed-landscape hardware (Fire TV, Android TV and most signage sticks ignore
// setRequestedOrientation - they can't physically rotate the panel). Resizes
// rootView to the rotated dimensions, recenters, and rotates. Covers single-zone
// (playerView/imageView/youtubeWebView) and multi-zone (ZoneManager renders into
// the same rootView). Values mirror the dashboard: landscape / portrait /
// landscape-flipped / portrait-flipped.
private fun applyOrientation(orientation: String) {
if (orientation == currentOrientation) return
currentOrientation = orientation
val m = resources.displayMetrics
val w = m.widthPixels.toFloat()
val h = m.heightPixels.toFloat()
val (rot, swap) = when (orientation) {
"portrait" -> 90f to true
"portrait-flipped" -> 270f to true
"landscape-flipped" -> 180f to false
else -> 0f to false // landscape
}
val lp = rootView.layoutParams
lp.width = (if (swap) h else w).toInt()
lp.height = (if (swap) w else h).toInt()
rootView.layoutParams = lp
rootView.translationX = if (swap) (w - h) / 2f else 0f
rootView.translationY = if (swap) (h - w) / 2f else 0f
rootView.rotation = rot
rootView.requestLayout()
Log.i("MainActivity", "Applied orientation: $orientation (rotation=$rot, swap=$swap)")
}
private fun setupServiceCallbacks() {
wsService?.onPlaylistUpdate = { data ->
try {
applyOrientation(data.optString("orientation", "landscape"))
// Check if device is suspended (trial expired / over limit)
if (data.optBoolean("suspended", false)) {
val message = data.optString("message", "Account Suspended")

View file

@ -192,8 +192,30 @@
// ---- playback ----
var player = new PlaylistPlayer(elStage, function () { return serverUrl.replace(/\/+$/, ''); });
// Rotate the playback stage in software for portrait / flipped signage. Tizen TVs
// are fixed-landscape, so we rotate the CONTENT (not the panel). Values mirror the
// dashboard: landscape / portrait / landscape-flipped / portrait-flipped.
function applyOrientation(o) {
var s = elStage;
if (!o || o === 'landscape') {
s.style.position = ''; s.style.top = ''; s.style.left = '';
s.style.width = ''; s.style.height = ''; s.style.transform = ''; s.style.transformOrigin = '';
return;
}
var deg = o === 'portrait' ? 90 : o === 'portrait-flipped' ? 270 : o === 'landscape-flipped' ? 180 : 0;
var swap = (deg === 90 || deg === 270);
s.style.position = 'absolute';
s.style.top = '50%';
s.style.left = '50%';
s.style.width = swap ? '100vh' : '100vw';
s.style.height = swap ? '100vw' : '100vh';
s.style.transformOrigin = 'center center';
s.style.transform = 'translate(-50%, -50%) rotate(' + deg + 'deg)';
}
function onPlaylist(payload) {
if (!payload) return;
applyOrientation(payload.orientation || 'landscape');
if (payload.suspended) {
player.stop();
elStage.innerHTML = '<div class="card" style="position:relative"><h1>' +