Skip to content

feat(chat): Record video messages from the voice message button - #2745

Draft
ToteMeiSter wants to merge 6 commits into
nextcloud:mainfrom
ToteMeiSter:feat/video-message-record
Draft

ToteMeiSter wants to merge 6 commits into
nextcloud:mainfrom
ToteMeiSter:feat/video-message-record

Conversation

@ToteMeiSter

@ToteMeiSter ToteMeiSter commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Draft: tested in CI only, not on a device (no Xcode/iPhone available to the author).

Refs #2742

Feature

The record button of the chat input can switch between voice and video messages. A tap toggles the mode (the choice is stored in the user defaults), holding the button records, like for voice messages: slide to cancel, slide up to lock. A video is at most 60 seconds long; too short videos are dropped like short voice messages. The front and the back camera can be switched while recording. The recording is sent as an ordinary file (MP4, H.264/AAC), without a voice message type.

The same feature for Android: nextcloud/talk-android#6812, PR nextcloud/talk-android#6818. Follow-ups in this stack: #2746 and #2747.

Changes

One commit.

  • VideoMessageRecorder (new): AVCaptureVideoDataOutput + AVCaptureAudioDataOutput feeding an AVAssetWriter, 1280x720 preset, portrait. AVCaptureMovieFileOutput is not used: removing the video input to switch the camera tears down its connection and ends the recording. The writer is not part of the capture graph. The audio session is restored afterwards.
  • BaseChatViewController+VideoMessage.swift (new): mode of the record button, gesture handling, live preview, 60 s limit timer. Not available in federated conversations, during a call, or without a camera.
  • BaseChatViewController: the voice message gestures call the video path when the mode is video; shareVoiceMessage is split so the upload (shareRecording) is shared. File names do not clash within one second. Voice messages behave as before.
  • CameraCaptureHelpers.swift (new): camera lookup, camera input replacement, rotation of the output, live preview view, audio session restore.
  • ExpandedVoiceMessageRecordingView: optional allowsPause (a video cannot be paused).
  • NCUserDefaults: stores the record button mode.
  • Localizable.strings: 7 new strings, all with matching comments. No plural strings.
  • Info.plist / InfoPlist.strings: the camera and microphone usage descriptions mention the video recording.

The branch does not depend on any other open PR.

How to test

Not built or run locally. Build and unit tests: CI only. CI in the fork on the same commit: Build, SwiftLint and all 5 test jobs (main, stable23, stable32, stable33, stable34) passed, run https://github.com/ToteMeiSter/talk-ios/actions/runs/37227740675. SwiftLint (swiftlint lint --quiet): no new warnings or errors compared to main. check-localizable-plurals.sh passes.

Manual, on a device (expected results, not verified):

  1. Open a conversation, tap the record button: the icon changes to a video camera and a hint is shown. Tap again: back to the microphone.
  2. In video mode hold the button: the camera preview and the timer are shown. Release: the video is uploaded as a file and appears in the chat.
  3. While holding, slide to the left: the recording is cancelled. Slide up: the recording is locked, then send or delete it.
  4. While recording, switch the camera: the recording continues.
  5. Hold for less than a second: nothing is sent. Hold for more than 60 seconds: the recording stops and is sent.
  6. Check the voice message mode for regressions: record, cancel, lock, send.
  7. Deny the camera or microphone permission: an error is shown, nothing is sent.

AI disclosure

This change was prepared with AI assistance (Claude Code, model claude-opus-5-5): code reading, the implementation, the tests and this description. Commits carry Assisted-by: Claude-Code:claude-opus-5-5. The change was reviewed by the author. It was not built or tested on a device (no Xcode or iPhone available to the author).

🤖 Generated with Claude Code

Let the user switch the record button of the chat input between voice and
video messages. Tapping the button toggles the mode (the choice is stored in
the user defaults), holding it records, like for voice messages: slide to cancel,
slide up to lock the recording, 60 seconds at most. Too short videos are dropped.
The front and the back camera can be switched while recording.

The video is recorded as H.264/AAC MP4 (1280x720 preset, portrait) with
AVCaptureVideoDataOutput and AVCaptureAudioDataOutput feeding an AVAssetWriter.
AVCaptureMovieFileOutput is not used: removing the video input to switch the
camera tears down its connection and ends the recording, the writer is not
part of the capture graph. The audio session is restored afterwards.

The recording is uploaded as an ordinary file. The upload path of voice
messages is shared (shareRecording), but without a temporary message and
without the voice message type. File names do not clash within one second.

The camera helpers (camera lookup, input replacement, rotation of the output,
live preview, audio session restore) live in CameraCaptureHelpers.swift.

The usage descriptions of camera and microphone mention the video recording.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
…a view

The preview of a video message follows the orientation of the interface, and
this reads it in one way, with portrait as long as it is not known.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
The preview of a video message recording is placed by a pure function in the
free space between the safe area and the recording panel: 9:16 in portrait and
16:9 in landscape, 16 pt from the edges, at most 75 % of the width. It is placed
again on rotation, when the panel of a locked recording appears and after the
camera was switched, and its frames follow the orientation of the interface.
The recorded file keeps the orientation the recording started in.

The chat is dimmed under the preview, which keeps the touches away from it, and
VoiceOver does not read the chat while recording.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
A locked video recording no longer opens the tall panel of the voice message:
it stays in the row of the inputbar, which shows the indicator and the time,
and gets a cancel button in the row and a send button over the record button.
The preview takes the freed height. Voice messages keep their panel, which
loses the pause switch that only videos needed.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
Overlays that appear while recording (pinned message, out of office) are added
below the dimming, VoiceOver gets its focus moved to the preview and does not
read the record button under the send button of a locked recording, the send
button uses the colors of the theme, the orientation of the start is read in
one way, and the test of the centering has room for rounding.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
The handler of the record button gets one branch more with the locked video
recording, which raised its cyclomatic complexity. The locking is its own
function now, and the handler is as complex as before.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
@ToteMeiSter

Copy link
Copy Markdown
Contributor Author

Update: five commits were added on top of the original change. They are the iOS counterpart of the video message preview and compact recording panel added to the Android PR nextcloud/talk-android#6818:

  • aec6897 — a helper for the interface orientation of a view;
  • db76e35 — lay out the video message preview like a camera view (dimmed chat, preview fitted to the recorded frame);
  • e94510d — keep a locked video recording in the row of the input bar (one-line panel);
  • fdb61ac — review follow-ups of the preview;
  • e30ed2a — move the locking of a recording out of the gesture handler (SwiftLint complexity).

They stay in this PR because they change the recording UI that this PR introduces. The same commits are carried into #2746 and #2747.

Tested in CI only, not on a device: build, SwiftLint and the unit/integration tests pass in the fork CI (run). The PR stays a draft until it is checked on an iPhone/iPad.

I also checked whether three issues found by review in the Android stack apply here; none does: the minimum voice length is measured from record() (recorder.currentTime), not from the touch; the video recorder state is released only after the writer finishes, and each recording has its own capture session and file; PHAuthorizationStatus.limited shows the grid with a banner, and the authorization callback re-reads the status instead of trusting the result.

This comment was prepared with the help of an AI assistant (Claude Code) and reviewed before posting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant