Platforms to show: All Mac Windows Linux Cross-Platform
Required plugins for this example: MBS MacBase Plugin, MBS AVFoundation Plugin, MBS MacCG Plugin, MBS MacCF Plugin, MBS Main Plugin
You find this example project in your Plugins Download as a Xojo project file within the examples folder: /AVFoundation/AVVideoWall
This example is the version from Sat, 21th Jun 2019.
Project "AVVideoWall.xojo_binary_project"
Class App Inherits Application
Const kEditClear = "&Löschen"
Const kFileQuit = "Beenden"
Const kFileQuitShortcut = ""
EventHandler Sub Open()
if AVAssetExportSessionMBS.available = false then
MsgBox "Please run on Mac OS X 10.7 or newer."
quit
end if
End EventHandler
End Class
Class Window1 Inherits Window
EventHandler Sub Close()
for each layer as AVCaptureVideoPreviewLayerMBS in videoPreviewLayers
layer.session = nil
next
if session<>nil then
session.stopRunning
session = nil
end if
window = nil
End EventHandler
EventHandler Sub Open()
call configure
End EventHandler
EventHandler Sub Resized()
Resize
End EventHandler
EventHandler Sub Resizing()
Resize
End EventHandler
Function Configure() As Boolean
// Create a screen-sized window and Core Animation layer
createWindowAndRootLayer
// Create a capture session
session = new AVCaptureSessionMBS
// Set the session preset
dim preset as string = AVFoundationMBS.AVCaptureSessionPreset640x480
session.SessionPreset = preset
// Create a wall of video out of the video capture devices on your Mac
dim success as boolean = setupVideoWall
return success
End Function
Sub Resize()
CATransactionMBS.begin
// Disable implicit animations for this transaction
CATransactionMBS.setValue(true, CATransactionMBS.kCATransactionDisableActions)
dim u as integer = UBound(videoPreviewLayers)
for i as integer = 0 to u
dim currentDevice as integer = i \ 4
dim rootBounds as CGRectMBS = rootlayer.bounds
dim deviceSquareBounds as CGRectMBS = CGRectMBS.Make(0, 0, rootBounds.size.width / devicesCount, rootBounds.size.height)
deviceSquareBounds.left = deviceSquareBounds.size.width * currentDevice
dim videoPreviewLayer as AVCaptureVideoPreviewLayerMBS = videoPreviewLayers(i)
dim q as integer = quadrantIndexes(i)
dim curLayerFrame as CGRectMBS = rectForQuadrant(q, deviceSquareBounds)
videoPreviewLayer.Frame = curLayerFrame
next
CATransactionMBS.commit
End Sub
Sub createWindowAndRootLayer()
window = window1.NSWindowMBS
// Make the content view layer-backed
dim windowContentView as NSViewMBS = window.contentView
windowContentView.wantsLayer = true
// Grab the Core Animation layer
rootLayer = windowContentView.layer
rootLayer.autoresizingMask = rootLayer.kCALayerHeightSizable + rootLayer.kCALayerWidthSizable
'rootLayer = window1.canvas1.calayermbs
// Set its background color to opaque black
dim colorspace as CGColorSpaceMBS = CGColorSpaceMBS.CreateDeviceRGB
dim black as new MemoryBlock(8*4)
black.SingleValue(0) = 0 // red
black.SingleValue(4) = 0 // green
black.SingleValue(8) = 0 // blue
black.SingleValue(12) = 1.0 // alpha
dim blackcolor as CGColorMBS = CGColorMBS.Create(colorspace, black)
rootLayer.backgroundColor = blackColor
// Show the window
window1.show
End Sub
Function devicesThatCanProduceVideo() As AVCaptureDeviceMBS()
dim devices() as AVCaptureDeviceMBS
dim allDevices() as AVCaptureDeviceMBS = AVCaptureDeviceMBS.devices
for each d as AVCaptureDeviceMBS in allDevices
if d.HasMediaType(AVFoundationMBS.AVMediaTypeVideo) or d.HasMediaType(AVFoundationMBS.AVMediaTypeMuxed) then
devices.Append d
end if
next
Return devices
End Function
Function rectForQuadrant(i as integer, rect as CGRectMBS) As CGRectMBS
dim curLayerFrame as new CGRectMBS(rect)
curLayerFrame.width = curLayerFrame.width / 2
curLayerFrame.height = curLayerFrame.height / 2
select case i
case 0 // top left
// currentLayerBounds.origin.x/y are unchanged.
case 1 // top right
curLayerFrame.left = curLayerFrame.left + curLayerFrame.width
case 2 // bottom left
curLayerFrame.top = curLayerFrame.top + curLayerFrame.height
case 3 // bottom right
curLayerFrame.top = curLayerFrame.top + curLayerFrame.height
curLayerFrame.left = curLayerFrame.left + curLayerFrame.width
end Select
// Make a 2-pixel border
curLayerFrame.top = curLayerFrame.top + 2
curLayerFrame.left = curLayerFrame.left + 2
curLayerFrame.width = curLayerFrame.width - 4
curLayerFrame.height = curLayerFrame.height - 4
return curLayerFrame
End Function
Function setupVideoWall() As Boolean
// Create 4 video preview layers per video device in a mirrored square, and
// set up these squares left to right within the root layer
dim error as NSErrorMBS
// Find video devices
dim devices() as AVCaptureDeviceMBS = devicesThatCanProduceVideo
devicesCount = UBound(devices)+1
dim currentDevice as integer = 0
dim rootBounds as CGRectMBS = rootlayer.bounds
if devicesCount = 0 then
return false
end if
// For each video device
for each d as AVCaptureDeviceMBS in devices
// Create a device input with the device and add it to the session
input = AVCaptureDeviceInputMBS.deviceInputWithDevice(d, error)
if error<>Nil then
MsgBox "deviceInputWithDevice: "+error.localizedDescription
return false
end if
session.addInputWithNoConnections input
// Find the video input port
dim videoPort as AVCaptureInputPortMBS = input.portWithMediaType(AVFoundationMBS.AVMediaTypeVideo)
// Set up its corresponding square within the root layer
dim deviceSquareBounds as CGRectMBS = CGRectMBS.Make(0, 0, rootBounds.size.width / devicesCount, rootBounds.size.height)
deviceSquareBounds.left = deviceSquareBounds.size.width * currentDevice
// Create 4 video preview layers in the square
for i as integer = 0 to 3
// Create a video preview layer with the session
videoPreviewLayer = AVCaptureVideoPreviewLayerMBS.layerWithSessionWithNoConnection(session)
// Add it to the arrays
videoPreviewLayers.append videoPreviewLayer
quadrantIndexes.append i
// Create a connection with the input port and the preview layer
// and add it to the session
dim connection as AVCaptureConnectionMBS = AVCaptureConnectionMBS.connectionWithInputPort(videoPort,videoPreviewLayer)
session.addConnection connection
// If the preview layer is at top-right (i=1) or bottom-left (i=2),
// flip it left-right.
dim doMirror as boolean = ((i = 1) or (i = 2))
if ( doMirror ) then
connection.AutomaticallyAdjustsVideoMirroring = false
connection.VideoMirrored = true
end if
// Compute the frame for the current layer
// Each layer fills a quadrant of the square
dim curLayerFrame as CGRectMBS = rectForQuadrant(i, deviceSquareBounds)
CATransactionMBS.begin
// Disable implicit animations for this transaction
CATransactionMBS.setValue(true, CATransactionMBS.kCATransactionDisableActions)
// Set the layer frame
videoPreviewLayer.Frame = curLayerFrame
// Save the frame in an array for the "sendLayersHome" animation
'[_homeLayerRects addObject:[NSValue valueWithRect:NSRectFromCGRect(curLayerFrame)]];
// We want the video content to always fill the entire layer regardless of the layer size,
// so set video gravity to ResizeAspectFill
videoPreviewLayer.VideoGravity = AVFoundationMBS.AVLayerVideoGravityResizeAspectFill
// If the layer is at top of the square (i=0, 1), make it upside down
if ( i < 2 ) then
connection.VideoOrientation = AVCaptureConnectionMBS.AVCaptureVideoOrientationPortraitUpsideDown
end if
// Add the preview layer to the root layer
rootLayer.addSublayer videoPreviewLayer
CATransactionMBS.commit
next
currentDevice = currentDevice +1
next
session.startRunning
return true
End Function
Property devicesCount As Integer
Property input As AVCaptureDeviceInputMBS
Property quadrantIndexes() As Integer
Property rootLayer As CALayerMBS
Property session As AVCaptureSessionMBS
Property videoPreviewLayer As AVCaptureVideoPreviewLayerMBS
Property videoPreviewLayers() As AVCaptureVideoPreviewLayerMBS
Property window As NSWindowMBS
End Class
MenuBar MenuBar1
MenuItem FileMenu = "&Ablage"
MenuItem FileQuit = "#App.kFileQuit"
MenuItem EditMenu = "&Bearbeiten"
MenuItem EditUndo = "&Rückgängig"
MenuItem UntitledMenu1 = "-"
MenuItem EditCut = "&Ausschneiden"
MenuItem EditCopy = "&Kopieren"
MenuItem EditPaste = "&Einfügen"
MenuItem EditClear = "#App.kEditClear"
MenuItem UntitledMenu0 = "-"
MenuItem EditSelectAll = "&Alles auswählen"
End MenuBar
ExternalFile info
End ExternalFile
End Project
The items on this page are in the following plugins: MBS AVFoundation Plugin.