나는 모 회사에서 OTT서비스와 라이브 스트리밍 서비스를 함께 개발하고 있다
당연히 회사에 들어오는 독점 컨텐츠도 꽤 많은 편인데.. 중요한 건 컨텐츠 화면에서 녹화가 가능해 불법 사이트로 유통이 많이 된다는 점, 그동안은 플랫폼 인지도를 위해 어느 정도 손해를 감수하고 풀어 두었지만 이제는 막을 때가 되었다는 내부의 목소리에 따라 앱 내에 캡처 방지 기능을 구현했다
* 제대로 막으려면 DRM을 적용해야하지만 개발에 시간이 걸려 일단 앱에 임시 조치가 취해졌다.
실제로 개발에 착수했을 때 안드로이드는 flutter_windowmanager 라이브러리로 코드 한줄만 쓰면 쉽게 막을 수 있었다 캡처하면 OS내에서 멘트도 출력해준다
// 캡처 방지 활성화
FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
// 캡처 방지 비활성화
FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE);
ios도 pub.dev에 몇개의 라이브러리가 올라와있었다
라이브러리를 까보니 MethodChannel
로 구현한 복잡하지 않은 로직이었고 굳이 라이브러리를 추가 할 정도 까진 아니어서 해당 링크를 참고해 구현했다 swift의 UITextField
개체를 이용한 방법인데 isSecureTextEntry를 활성화 하면 ios에서 보안으로 캡처,녹화를 막는다고 한다.(참조)
isSecureTextEntry
A Boolean value that indicates whether a text object disables copying, and in some cases, prevents recording/broadcasting and also hides the text.
주석 아래 코드만 추가해주면 된다
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
//UITextField 초기화
private var textField = UITextField()
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
//함수 추가
makeSecureYourScreen()
//MethodChannel생성
let controller : FlutterViewController = self.window?.rootViewController as! FlutterViewController
let securityChannel = FlutterMethodChannel(name: "secureShotChannel", binaryMessenger: controller.binaryMessenger)
securityChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if call.method == "secureIOS" {
self.textField.isSecureTextEntry = true
} else if call.method == "unSecureIOS" {
self.textField.isSecureTextEntry = false
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
//UITextField View에 추가
private func makeSecureYourScreen() {
if (!self.window.subviews.contains(textField)) {
self.window.addSubview(textField)
textField.centerYAnchor.constraint(equalTo: self.window.centerYAnchor).isActive = true
textField.centerXAnchor.constraint(equalTo: self.window.centerXAnchor).isActive = true
self.window.layer.superlayer?.addSublayer(textField.layer)
textField.layer.sublayers?.first?.addSublayer(self.window.layer)
}
}
}
abstract class SecureShot {
SecureShot._();
static const _channel = MethodChannel('secureShotChannel');
static void on() {
if (Platform.isAndroid) {
FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
} else if (Platform.isIOS) {
_channel.invokeMethod("secureIOS");
}
}
static void off() {
if (Platform.isAndroid) {
FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE);
} else if (Platform.isIOS) {
_channel.invokeMethod("unSecureIOS");
}
}
}
void initState() {
super.initState();
SecureShot.on();
}
void dispose() {
SecureShot.off();
super.dispose();
}
}
혹시 틀린 부분이 있다면 댓글 주세요!!!
오 네이티브 레벨로 해주지 않아도 되는군요 좋네요..!
OS 메세지 어떻게 뜨는지도 궁금하네요 ㅎㅎ