How do I layout my React Native Fire TV SubtitleView correctly?

363 views Asked by At

I am working on a React Native implementation of the Bitmovin player using their Android SDK. At this stage, I'm not sure how specific this is to the Bitmovin player, but as they don't officially support React Native at this stage, I want to ask about this on SO first. This is a React Native UI Component with a custom view, using a layout file. I am trying to present a subtitle view on top of a player view, and I have based my layout on Bitmovin's simple examples. In fact I have simplified the layout even further:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <com.bitmovin.player.PlayerView
        android:id="@+id/bitmovinPlayerView"
        app:shutter_background_color="@android:color/transparent"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/bootsplash_background">

        <com.bitmovin.player.SubtitleView
            android:id="@+id/bitmovinSubtitleView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:foregroundGravity="center" />

    </com.bitmovin.player.PlayerView>
</LinearLayout>

This presents the SubtitleView at the top of the screen. Nothing I have tried so far presents the SubtitleView at the bottom of the screen in the more common position. I have experimented with every single parameter on all of these elements, as far as I can tell. Here is the code that initialises the view:

 public void init() {
    inflate(context, R.layout.player_container, this);

    StyleConfig styleConfig = new StyleConfig();
    styleConfig.setUiEnabled(false);

    PlayerConfig playerConfig = new PlayerConfig();
    playerConfig.setStyleConfig(styleConfig);

    playerView = findViewById(R.id.bitmovinPlayerView);
    player = Player.create(context, playerConfig);

    playerView.setPlayer(player);

    player.on(SourceEvent.Loaded.class, this::onLoad);
    player.on(PlayerEvent.Playing.class, this::onPlay);
    player.on(PlayerEvent.Paused.class, this::onPause);
    player.on(PlayerEvent.Seek.class, this::onSeek);
    player.on(PlayerEvent.TimeChanged.class, this::onTimeChanged);
    player.on(PlayerEvent.Destroy.class, this::onDestroy);
    player.on(PlayerEvent.Seeked.class, this::onSeeked);
    player.on(PlayerEvent.PlaybackFinished.class, this::onPlaybackFinished);
    player.on(PlayerEvent.Ready.class, this::onReady);
    player.on(SourceEvent.Error.class, this::onError);
    player.on(SourceEvent.SubtitleChanged.class, this::onSubtitleChanged);
    player.on(PlayerEvent.Error.class, this::onError);


    subtitleView = findViewById(R.id.bitmovinSubtitleView);
    subtitleView.setPlayer(player);

    player.setVolume(100);
}

I have read that React Native styles the top-level view of a UI Component, so this is my only clue at this stage. I'm unsure how to respond to that info however...

EDIT: The problem is likely to be that dynamically updating view layouts in Android in React Native is not straightforward. This has been discussed at length here.

EDIT 2: I have tried to listen for global layout changes, which is one of the proposed workarounds for view layout issues:

        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            requestLayout();
        }
    });

This is called as expected, but has no effect on the subtitleView, which still displays at the top of the player, which seems to be because it has a height of 0.

EDIT 3: Another suggested solution that didn't work for me:

    private void setupLayoutHack() {

        Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
                manuallyLayoutChildren();
                getViewTreeObserver().dispatchOnGlobalLayout();
                Choreographer.getInstance().postFrameCallback(this);
            }
        });
    }

    private void manuallyLayoutChildren() {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
            child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
        }
    }

I called setupLayoutHack() in the constructor but saw no difference after applying those changes either :(

EDIT 4: My final attempt at fixing the SubtitleView layout was experimenting with measuring and laying out in various ways:

    private void refreshViewChildrenLayout(View view){
        view.measure(
                View.MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(), View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(), View.MeasureSpec.EXACTLY));
        view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
    }

However, the height in all cases that I tried was 0, which meant nothing was altered. There is a solution mentioned in the above RN issue that suggests that the shadow node for the subtitle view should be overridden. So one way forward could be to build a new subtitle view that has that included.

However, at this stage it seems to me an easier approach to respond to subtitle cues in React Native and perform all display and styling there.

(There is also a lesser issue of how to make the background on either side of the text transparent, but the layout issue is far more important at this stage).

Problem

1

There are 1 answers

0
jarhoax On

Disclaimer: I'm not very familiar with React Native and how it influences layout creation if at all.

However looking at your layout file, it indicates that the SubtitleView is the top child of the PlayerView, which is a FrameLayout, thus gets added at the top (left). By specifying android:layout_height="wrap_content" on the SubtitleView it will only take up space that is required by the view. In the Bitmovin sample, it is generated in code and therefore should inherit the attributes from the parent, which is a RelativeLayout with android:layout_weight="1" which results in stretching it's height to the space available.

Long story short, try setting the height of your SubtitleView to match_parent