Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
8 changes: 5 additions & 3 deletions Basic-Video-Chat/Basic-Video-Chat/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ViewController: UIViewController {

var publisher: OTPublisher?
var subscriber: OTSubscriber?

let renderer = CustomVideoRender()
override func viewDidLoad() {
super.viewDidLoad()

Expand Down Expand Up @@ -61,13 +61,15 @@ class ViewController: UIViewController {

let settings = OTPublisherSettings()
settings.name = UIDevice.current.name
publisher = OTPublisher(delegate: self, settings: settings)!

publisher = OTPublisher(delegate: self, settings: settings)!
publisher?.videoRender = renderer
session.publish(publisher!, error: &error)

if let pubView = publisher!.view {
pubView.frame = CGRect(x: 0, y: 0, width: kWidgetWidth, height: kWidgetHeight)
renderer.renderView.frame = pubView.frame
view.addSubview(pubView)
pubView.addSubview(renderer.renderView)
}
}

Expand Down
404 changes: 404 additions & 0 deletions Basic-Video-Renderer/Basic-Video-Renderer.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1170"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F86C64991D5C7C630081846D"
BuildableName = "Basic-Video-Renderer.app"
BlueprintName = "Basic-Video-Renderer"
ReferencedContainer = "container:Basic-Video-Renderer.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F86C64991D5C7C630081846D"
BuildableName = "Basic-Video-Renderer.app"
BlueprintName = "Basic-Video-Renderer"
ReferencedContainer = "container:Basic-Video-Renderer.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F86C64991D5C7C630081846D"
BuildableName = "Basic-Video-Renderer.app"
BlueprintName = "Basic-Video-Renderer"
ReferencedContainer = "container:Basic-Video-Renderer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F86C64991D5C7C630081846D"
BuildableName = "Basic-Video-Renderer.app"
BlueprintName = "Basic-Video-Renderer"
ReferencedContainer = "container:Basic-Video-Renderer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
20 changes: 20 additions & 0 deletions Basic-Video-Renderer/Basic-Video-Renderer/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// AppDelegate.swift
// Hello-World
//
// Created by Roberto Perez Cubero on 11/08/16.
// Copyright © 2016 tokbox. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
104 changes: 104 additions & 0 deletions Basic-Video-Renderer/Basic-Video-Renderer/CustomRenderView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//
// CustomRenderView.swift
// Basic-Video-Renderer
//
// Created by Artur Osiński on 27/10/2025.
// Copyright © 2025 tokbox. All rights reserved.
//
import OpenTok

class CustomRenderView: UIView {

// MARK: - Properties
var renderQueue: DispatchQueue
private var image: CGImage?

// MARK: - Init
override init(frame: CGRect) {
self.renderQueue = DispatchQueue.global(qos: .userInitiated)
super.init(frame: frame)
self.image = nil
}

required init?(coder: NSCoder) {
self.renderQueue = DispatchQueue.global(qos: .userInitiated)
super.init(coder: coder)
self.image = nil
}

// MARK: - Render Video Frame
func renderVideoFrame(_ frame: OTVideoFrame) {
let frameToRender = frame

renderQueue.sync {
// Release previous image if any exists
if image != nil {
image = nil
}
guard let format = frame.format else { return }
let width = Int(format.imageWidth)
let height = Int(format.imageHeight)
let bufferSize = width * height * 3

guard let rawYPlane = frameToRender.planes?.pointer(at: 0) else { return }
let yplane = rawYPlane.bindMemory(to: UInt8.self, capacity: width * height)
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)

// Fill RGB buffer with grayscale image (Y only)
for i in 0..<height {
for j in 0..<width {
let pixelIndex = (i * width * 3) + (j * 3)
let yValue = yplane[(i * width) + j]
buffer[pixelIndex] = yValue
buffer[pixelIndex + 1] = yValue
buffer[pixelIndex + 2] = yValue
}
}

// Release buffer when CGDataProvider is done
let releaseCallback: CGDataProviderReleaseDataCallback = { _, data, _ in
data.deallocate()
}

guard let provider = CGDataProvider(dataInfo: nil, data: buffer, size: bufferSize, releaseData: releaseCallback) else {
buffer.deallocate()
return
}

// Create CGImage
image = CGImage(
width: width,
height: height,
bitsPerComponent: 8,
bitsPerPixel: 24,
bytesPerRow: 3 * width,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue),
provider: provider,
decode: nil,
shouldInterpolate: false,
intent: .defaultIntent
)

DispatchQueue.main.async { [weak self] in
self?.setNeedsDisplay()
}
}
}

// MARK: - Drawing
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
var imgCopy: CGImage?

renderQueue.sync {
if let currentImage = image {
imgCopy = currentImage.copy()
}
}

if let img = imgCopy {
context.draw(img, in: self.bounds)
}
}
}
25 changes: 25 additions & 0 deletions Basic-Video-Renderer/Basic-Video-Renderer/CustomVideoRender.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// CustomVideoRender.swift
// Basic-Video-Renderer
//
// Created by Artur Osiński on 27/10/2025.
// Copyright © 2025 tokbox. All rights reserved.
//

import OpenTok

class CustomVideoRender: NSObject, OTVideoRender {

var renderView: UIView

override init() {
// Initialize with your custom render view
self.renderView = CustomRenderView(frame: .zero)
super.init()
}

func renderVideoFrame(_ frame: OTVideoFrame) {
// Cast to your custom view type and pass the frame for rendering
(renderView as? CustomRenderView)?.renderVideoFrame(frame)
}
}
Loading