Skip to content

DISC-433: Mute/un-mute controls - #2556

Merged
Arkariang merged 6 commits into
masterfrom
imartin/disc-433
Aug 6, 2026
Merged

DISC-433: Mute/un-mute controls#2556
Arkariang merged 6 commits into
masterfrom
imartin/disc-433

Conversation

@Arkariang

@Arkariang Arkariang commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📲 What

Adds a mute / unmute control to the VideoFeed player. Each video now shows a circular mute toggle just below the play button; tapping it silences or restores the video's audio. The choice persists as you scroll through the feed until you change it, and every toggle is tracked as an analytics event.

🤔 Why

The VideoFeed autoplays videos, but until now there was no way for a user to control audio — videos always played at full volume with no escape hatch, and no way to turn sound on if they wanted it. A mute toggle is table-stakes for a vertical video feed (TikTok/Reels-style) and was called for in the Mobile Video Discovery designs. We also want to measure how often people mute/unmute to inform future audio defaults.

🛠 How

State — a single isMuted flag is hoisted in VideoFeedScreen above the pager, so it's shared across all pages and survives configuration changes. Default is unmuted. It's passed into every page's KSVideoPlayer.
Player — KSVideoPlayer gains isMuted / onMuteToggle(isMuted) params. A LaunchedEffect(exoPlayer, isMuted) applies exoPlayer.volume = if (isMuted) 0f else 1f, keyed on the player too so a freshly created player after a swipe inherits the current preference.

UI — a glassmorphism centered just below the play control. The icon reflects the current state. It appears and disappears in lockstep with the play/pause control (same showControls flag, same fade+scale transition), so the two read as one controls cluster. the Mute/VolumeUp vector icons were added to Icons.kt;

Analytics — mirrors the existing play/pause pattern. Two new CtaContextName values (video_mute, video_unmute); trackVideoFeedCTAClicked fires a single CTA Clicked event with context_page = video_feed, context_cta = video_mute | video_unmute, and the project/video ids.

Analytics ID fix (all CTAs) — while wiring this up, review surfaced that trackVideoFeedCTAClicked was reporting video_feed_video_id = project.id(), inconsistent with the impression / progress-bar events which use the video's own item.videoId. Fixed by threading the real videoId through the CTA pipeline (VideoFeedScreen → VideoFeedActivity → VideoFeedViewModel.onCTAClicked → AnalyticEvents). This corrects video_feed_video_id for every VideoFeed CTA event — play/pause, save, creator, share, and the new mute/unmute — not just this mute/unmute feature.

Tests:

  • KSVideoPlayerTest (visibility tied to controls, volume application, reported toggle state),
  • VideoFeedScreenTest (label toggle, callback reports project + resulting state),
  • SegmentTest (mute/unmute event properties).

👀 See

mute.mp4
Analitics

| --- | --- |
| | |

📋 QA

  • Open the videofeed, make sure volume is up to hear sound. Hit the middel of the screen, overlay with pause/mute buttons appear. Mute and play video again, swipe to make sure the state is persisted. Unmute again

Story 📖

DISC-443

@codecov-commenter

codecov-commenter commented Aug 5, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 91.44144% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.28%. Comparing base (88a4833) to head (a96607b).

Files with missing lines Patch % Lines
...ui/compose/designsystem/videoplayer/icons/Icons.kt 94.38% 10 Missing ⚠️
.../compose/designsystem/videoplayer/KSVideoPlayer.kt 76.92% 1 Missing and 8 partials ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #2556      +/-   ##
============================================
+ Coverage     65.15%   65.28%   +0.12%     
+ Complexity     2564     2563       -1     
============================================
  Files           405      405              
  Lines         32467    32686     +219     
  Branches       4612     4630      +18     
============================================
+ Hits          21155    21340     +185     
- Misses         8889     8916      +27     
- Partials       2423     2430       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Arkariang
Arkariang requested a review from Copilot August 5, 2026 22:30
@Arkariang Arkariang changed the title Imartin/disc 433 DISC-433: Mute/un-mute controls Aug 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a mute/unmute control to the VideoFeed video player, hoisting mute state to the feed level so the preference persists across swipes and wiring the toggle into existing CTA-click analytics.

Changes:

  • Add isMuted + onMuteToggle API to KSVideoPlayer, apply mute via ExoPlayer.volume, and render a glassmorphism mute/unmute button alongside existing controls.
  • Hoist mute state in VideoFeedScreen using rememberSaveable, forward toggle callbacks up to VideoFeedActivity for analytics.
  • Extend analytics context values and add/expand Robolectric tests for player behavior, feed UI behavior, and Segment event properties.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
app/src/main/java/com/kickstarter/ui/compose/designsystem/videoplayer/KSVideoPlayer.kt Adds mute state plumbing, volume application, and a new animated mute/unmute control.
app/src/main/java/com/kickstarter/features/videofeed/ui/VideoFeedScreen.kt Hoists isMuted with rememberSaveable and forwards mute toggles to callers.
app/src/main/java/com/kickstarter/features/videofeed/ui/VideoFeedActivity.kt Tracks mute/unmute as CTA क्लिक events via the existing ViewModel pipeline.
app/src/main/java/com/kickstarter/libs/utils/EventContextValues.kt Adds VIDEO_MUTE / VIDEO_UNMUTE CTA context values.
app/src/main/res/values/strings.xml Adds localized strings for “Mute” / “Unmute”.
app/src/main/java/com/kickstarter/ui/compose/designsystem/videoplayer/icons/Icons.kt Adds Mute and VolumeUp vector assets (and previews).
app/src/main/java/com/kickstarter/ui/compose/designsystem/KSDimensions.kt Introduces size/offset dimensions for positioning the mute control.
app/src/test/java/com/kickstarter/ui/compose/designsystem/videoplayer/KSVideoPlayerTest.kt Adds unit tests for mute button visibility, callback state, and volume changes.
app/src/test/java/com/kickstarter/features/videofeed/ui/VideoFeedScreenTest.kt Adds integration-style tests for mute UI labeling and callback reporting.
app/src/test/java/com/kickstarter/libs/SegmentTest.kt Adds Segment property assertions for mute/unmute CTA-clicked events.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/src/test/java/com/kickstarter/libs/SegmentTest.kt
@Arkariang Arkariang self-assigned this Aug 6, 2026
@Arkariang
Arkariang requested a review from Copilot August 6, 2026 16:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (4)

app/src/main/java/com/kickstarter/ui/compose/designsystem/KSDimensions.kt:104

  • These new dimension fields default to Dp.Unspecified, but they’re used directly in Modifier.size(...) / Modifier.offset(...) (in KSVideoPlayer). If any theme/dimension preset instantiates KSDimensions without explicitly setting these, it can lead to invalid layout values at runtime. Prefer providing safe non-Unspecified defaults (matching KSStandardDimensions), or ensure every KSDimensions preset in the codebase sets these fields.
    val videoFeedCloseButtonSize: Dp = Dp.Unspecified,
    val videoFeedCloseButtonTopPadding: Dp = Dp.Unspecified,
    val videoFeedHideUiIconSize: Dp = Dp.Unspecified,
    val videoFeedMuteButtonSize: Dp = Dp.Unspecified,
    val videoFeedMuteButtonCenterOffset: Dp = Dp.Unspecified,

app/src/main/java/com/kickstarter/ui/compose/designsystem/videoplayer/KSVideoPlayer.kt:324

  • Unmuting forces the player's volume to 1f, which overwrites any pre-configured/custom player volume (e.g., if the caller set a different volume for mixing). Consider capturing the player's previous volume when muting and restoring it on unmute (or accepting a caller-provided unmutedVolume), so mute toggling doesn’t unexpectedly change intended volume levels.
    LaunchedEffect(exoPlayer, isMuted) {
        exoPlayer.volume = if (isMuted) 0f else 1f
    }

app/src/main/java/com/kickstarter/ui/compose/designsystem/videoplayer/KSVideoPlayer.kt:699

  • The control is a toggle, but semantics only expose an action label (“Mute”/“Unmute”). For assistive tech, it’s typically clearer to expose both control identity and current state (e.g., stateDescription of “Muted/Unmuted”, or use toggle semantics like toggleable with a role) rather than only the action text. This improves screen reader clarity without changing the UI.
        val label = stringResource(id = if (isMuted) R.string.fpo_Unmute else R.string.fpo_Mute)
        KSControlIcon(
            icon = if (isMuted) Mute else VolumeUp,
            size = dimensions.videoFeedMuteButtonSize,
            onClick = onClick,
            hazeState = hazeState,
            contentDescription = label,
            onClickLabel = label,
            modifier = Modifier.testTag(KSVideoPlayerTestTag.VIDEO_PLAYER_MUTE_BUTTON.name)
        )

app/src/test/java/com/kickstarter/features/videofeed/ui/VideoFeedScreenTest.kt:862

  • These assertions hardcode the English accessibility labels ("Mute"/"Unmute"). Since the implementation uses string resources, tests will become locale-sensitive (and can fail if the test environment runs under a non-English locale or if strings change). Prefer fetching the expected strings from resources (e.g., via a context) and asserting against those values.
        composeTestRule.onNodeWithContentDescription("Mute", useUnmergedTree = true)
            .assertDoesNotExist()

        // - Tapping the video reveals the controls; videos start unmuted, so the action is "Mute".
        composeTestRule.onNodeWithTag(KSVideoPlayerTestTag.VIDEO_PLAYER_SURFACE.name).performClick()
        composeTestRule.waitForIdle()
        composeTestRule.onNodeWithContentDescription("Mute", useUnmergedTree = true)
            .assertIsDisplayed()
            .performClick()
        composeTestRule.waitForIdle()

        // - After tapping it is muted, so the button's action becomes "Unmute".
        composeTestRule.onNodeWithContentDescription("Unmute", useUnmergedTree = true)
            .assertIsDisplayed()
    }

@Arkariang
Arkariang marked this pull request as ready for review August 6, 2026 17:37
@Arkariang
Arkariang requested a review from tonyteate August 6, 2026 17:37

@tonyteate tonyteate left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@Arkariang
Arkariang merged commit f40b4dd into master Aug 6, 2026
3 of 4 checks passed
@Arkariang
Arkariang deleted the imartin/disc-433 branch August 6, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants